PHP のオートローダーでハマった話
こんにちは。沖縄移住組 IT エンジニアの勇大(@yudy1152)です。
最近は Laravel を触っています。
クラスを新しく追加したけれども、実行時に not found となり、どうもうまくオートロードしてくれなくてハマってしまったので、調査内容や解決方法を書き留めておこうと思います。
※ファイル構成やクラス名などはこの記事を書くにあたって簡易的にしたものです。
目次
not found and could not be autoloaded.
クラスやインターフェースを追加してテストを実行した時に、以下のようなエラーメッセージが出てしまいました。
Fatal error: Interface 'App\Test\TestInterfaceA' not found in /var/www/app/Test/TestA.php on line 5
静的解析ツール PHPStan を導入していたので実行してみたところ、以下のようなメッセージが出ました。(一部抜粋)
> phpstan analyse Cannot load Xdebug - it was already loaded Note: Using configuration file /var/www/phpstan.neon. 59/59 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% ------ --------------------------------------------------------------------- Line app/Test/TestA.php ------ --------------------------------------------------------------------- Class App\Test\TestInterfaceA not found and could not be autoloaded. ------ ---------------------------------------------------------------------
理由は分からないけれども、オートロードしてくれないようで、以下のコマンドをとりあえず実行しました。
$ composer dump-autoload
このコマンドを叩いた後、テストを実行すると正常に完了しました。
え?毎回実行しなきゃいけないの?
dump-autoload
を実行した時にそれなりに時間がかかりました。(数十秒〜約1分)
クラスを新しく追加した時に毎回実行しないといけないのか、いやそんなことはないよね、何かしらの設定が足りてないのか間違ってるのか・・・?
ということで、調査し、解決できたので分かった事などを書きます。
ファイルの構成
ファイル構成は以下のようになっていました。
interface 2 つを TestInterfaces.php で定義。それぞれの interface を実装するクラスを 2 つ用意、という構成です。
- ファイル構成
app ┗ Test ┣ TestA.php ┣ TestB.php ┗ TestInterfaces.php
- TestA.php
namespace App\Test; class TestA implements TestInterfaceA { public function outputA() { echo 'Test A !!!'; } }
- TestB.php
namespace App\Test; class TestB implements TestInterfaceB { public function outputB() { echo 'Test B !!!'; } }
- TestInterfaces.php
namespace App\Test; interface TestInterfaceA { public function outputA(); } interface TestInterfaceB { public function outputB(); }
調査開始!
まず確認したことは、composer.json
です。
autoload に関する部分は以下のように定義していて、app
以下はちゃんとオートロードしてくれるはずの設定になっています。
ふむふむ、PSR-4 の規約にのっとってオートロードしてくれるのね。その規約ってなんだろ?が、正直なところでした。
"autoload": { "psr-4": { "App\\": "app/" } },
次に確認したことは、vendor/composer/autoload_classmap.php
でした。
$ composer dump-autoload
を実行する前と後でどう変わるのか、を確認したところ、実行後には以下のようなマッピングがファイル内に追加されていました。
'App\\Test\\TestA' => $baseDir . '/app/Test/TestA.php', 'App\\Test\\TestB' => $baseDir . '/app/Test/TestB.php', 'App\\Test\\TestInterfaceA' => $baseDir . '/app/Test/TestInterfaces.php', 'App\\Test\\TestInterfaceB' => $baseDir . '/app/Test/TestInterfaces.php',
ここで思ったことは、composer.json
でオートロードするための設定があるのに、autoload_classmap.php
にも定義が必要なの?です。
次に、そのクラスのみで完結する TestC を追加してみました。(継承などしないクラス)
namespace App\Test; class TestC { public function outputC() { echo 'Test C !!!'; } }
TestA, TestB はいったん無視し、TestC の function のみを実行してみたところ正常に完了しました。
TestC を追加した後、$ composer dump-autoload
は実行していないので、autoload_classmap.php
にはもちろん TestC に関する定義はありません。
設定などの環境まわりというよりも、クラス内の構成について疑いが・・・
ここでようやっと基本に立ち返り、composer.json
内の autoload
で指定している PSR-4
について調べました。
結論:ファイル名とクラス名を同じにしよう!
PSR-4
について参考にしたサイトがこちらです。
私が今回ハマった内容に該当する箇所は、「ファイルのロード」項目の 4. でした。
4. ファイル名は終端クラス名の大文字と一致する必要があります。
このルールに沿って TestInterfaces.php 内に定義していた各 interface を、インターフェース名と同じファイルを用意し、以下のような構成に作り替えました。
- ファイル構成
app ┗ Test ┣ TestA.php ┣ TestB.php ┣ TestInterfaceA.php ┗ TestInterfaceB.php
- TestInterfaceA.php
interface TestInterfaceA { public function outputA(); }
- TestInterfaceB.php
interface TestInterfaceB { public function outputB(); }
作り替えた後、autoload_classmap.php
に APP\Test
に関する定義が存在しないことを確認し、テスト実行をすると、無事に正常完了することができました!
まとめ
PSR に興味を持ちました
- 今までは、PHP について調べた時に、PSR の内容が書かれている記事がヒットして参考にする程度でした。 PSR を必ず守らないとダメ!というものでもないようですが、どんな内容なのか一度熟読してみようと思います。
PSR に興味を持つついでに PHP もちゃんと勉強しよう!と思いました
- 早速仕事帰りに買った本
TECHNICAL MASTER はじめてのPHPプロフェッショナル開発 PHP7対応
- 作者: 伊藤翔,金城秀樹,高野福晃,永井勝一郎
- 出版社/メーカー: 秀和システム
- 発売日: 2019/02/26
- メディア: 単行本
- この商品を含むブログを見る
- この本を読んで実際に取り組んでみたことや、役立てたことなど書いていけたらいいな、と思います。(まだ1ページも読んでませんが!)
- 早速仕事帰りに買った本