Composerのautoloaderの最適化の仕様について
こんにちは、ナカエです。
Composerのautoloaderの最適化の挙動について、思わぬ混乱を招いたことがあったので書き留めておきます。
現象
とあるPHPのプロジェクトで、ローカル環境ではclass not foundとなってテストが落ちるのに、違う環境では該当クラスを用いた処理が動いている、という一見不思議な現象が起こっていました。
本番環境でclass not foundになっていたとすれば酷い話ですが、幸い実害にはなっていませんでした。納得がいかなかったので調べた結果、Composerのautoloader生成の仕様によるものだということがわかりました。
以下、現象を再現するためのコード例になります。
準備
新規にComposrを利用するプロジェクトを作成します。
$ mkdir phantom_load
$ cd phantom_load
$ touch composer.json
composer.json
PSR-4の規則で、src以下をApp名前空間に対応させる設定にします。
{
"name": "nextat/phantom_autoload",
"type": "project",
"require": {},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
src/Service/Greeter.php
PSR-4として正しくない名前空間を持つクラスを作成します。
※PSR-4に従えば、このクラスの名前空間は\App\Serviceであるべきです。
<?php namespace App;
class Greeter {
public function hello()
{
return 'hello';
}
}
main.php
先ほどのクラスを利用する処理を作成します。
<?php
require __DIR__ . '/vendor/autoload.php';
$greeting = (new \App\Greeter())->hello();
echo $greeting;
最適化しないautoloaderを用いた場合
$ composer dump-autoload
Generating autoload files
$ php main.php
PHP Fatal error: Class 'App\Greeter' not found in /Users/Hoge/phantom_autoload/main.php on line 6
クラスが見つからないという旨のエラーが発生します。
最適化したautoloaderを用いた場合
$ composer dump-autoload --optimize
Generating optimized autoload files
$ php main.php
hello
エラーなしに実行できてしまいます。
この際、Composerが最適化したautoloaderの中身を見てみると、先ほど作ったクラスがclassmapで登録されていることがわかります。
vendor/composer/autoload_classmap.php
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'App\\Greeter' => $baseDir . '/src/Service/Greeter.php',
);
vendor/composer/autoload_static.php
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitd48f6624ca191eb3306c9e66ddc7fa3d
{
public static $prefixLengthsPsr4 = array (
'A' =>
array (
'App\\' => 4,
),
);
public static $prefixDirsPsr4 = array (
'App\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
);
public static $classMap = array (
'App\\Greeter' => __DIR__ . '/../..' . '/src/Service/Greeter.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitd48f6624ca191eb3306c9e66ddc7fa3d::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitd48f6624ca191eb3306c9e66ddc7fa3d::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitd48f6624ca191eb3306c9e66ddc7fa3d::$classMap;
}, null, ClassLoader::class);
}
}
まとめ
- Composerで最適化したautoloaderを生成した場合、PSR-4指定のディレクトリ以下のファイルはPSR-4として正しいかどうかにかかわらず、classmapで読み込まれるようになる
- 本番では最適化したautoloaderを使うことが多いと思われるので大した問題にはならないが、他の環境と設定が異なる場合は注意が必要