背景
PHP PHP

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を使うことが多いと思われるので大した問題にはならないが、他の環境と設定が異なる場合は注意が必要
≪ Firebaseが使い勝手よすぎた  |  jqueryで複数非同期終了後に何かを実行したいとき ≫

Web制作のお問い合わせ

075-744-6842

(平日/土曜 10:00~17:00)

 お問い合わせ