public/index.php の最初の三行(vendor/autoload.php の挙動)
public/index.php の最初の三行を解読していきましょう。
まず初めの2行。
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
については、 namespaceとuse を改めて読んでみましょう。
端的には「Requestクラスと Responseクラスを使うよ」という宣言になります。
コード位置は、多少の推測をもって「vendorの中、あたりにあるんだろうなぁ」くらいから推測をしていくと
vendor/psr/http-message/src/ServerRequestInterface.php
vendor/psr/http-message/src/ResponseInterface.php
にあることが、比較的簡単に探し出せます。
これらはclassではなくinterfaceなので、なんとなく「あぁこんなメソッドがあるんだなぁ」くらいに眺めておくと、後々、便利かもしれません。
さて。
本文の主題であるvendor/autoload.php について、少しあちこちすっ飛ばしながらになりますが、読んでいってみましょう。
幾分ボリューミーですので、頑張って読み進めてください。
上述のファイル(vendor/autoload.php)自体は
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91::getLoader();
といった感じで、ごく簡単に書かれています。
まず「 __DIR__ 」についてわからない方は こちら(自動的に定義される定数) を確認をしてもらいつつ、実際のファイルである autoload_real.php を覗いてみましょう。
また、 :: という書き方は、直接的には スコープ定義演算子 というもの、になりますので、こちらも併せて読んでおくとよいでしょう。
少し余裕があるようであれば、 3つのstatic についても読んでおくと、知識の幅が広がるかもしれません。
さて。
今手元で見ているバージョンだと、以下のようなコードになっています。
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire55b9de9bcb64d89e3a574767b84d6d91($fileIdentifier, $file);
}
return $loader;
}
先頭から、ゆっくりと読み解いていってみましょう。
まずここですね。
if (null !== self::$loader) {
return self::$loader;
}
self::がわからないひとは こちら( スコープ定義演算子 と、余裕があれば 遅延静的束縛 ) を読んでください。
一端、このタイミングでは「間違いなくnull」なので、if文は飛ばしていきます(後で出てきます)。
次に出てくるのが
こちらですね。
spl_autoload_register(array('ComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91', 'loadClassLoader'), true, true);
spl_autoload_register(あるいはautoload)については autoloadについて を読んでいただきつつ。
spl_autoload_register( https://www.php.net/manual/ja/function.spl-autoload-register.php )の引数を見てみると
・第一引数はcallable( こちら 参照)で、配列なので「クラス名+メソッド名」
・第二引数がtrueなので「なんかあったら例外をぶん投げる」指定
・第三引数がtrueなので「第一引数のcallableを最優先で!!」
という感じになります。
第一引数に注目、クラス名のComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91は「自分自身」になりますので、自分自身の中にある「loadClassLoader」をautoloaderとして積んでます。
コードを見てみましょう。
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
割と身も蓋もないですね
・もし「引数で与えられているクラス名」が「Composer\Autoload\ClassLoader」だったら
・__DIR__ . '/ClassLoader.php' を読み込む
という、クラスを思いっきり限定したautoloaderになります。
で、spl_autoload_registerの次の行に
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
とありますので、まさに「Composer\Autoload\ClassLoader」がnewされるので、このタイミングで「__DIR__ . '/ClassLoader.php'」が読み込まれます。
では、ClassLoader.phpを見てみましょう。
とりあえず
class ClassLoader
が定義されています。当たり前ではありますが、こーゆー確認って割と大事なんですよねぇ。
少し行数があるので(現在の手元のバージョンで、445行)、必要な時に読んでいきましょう。
とりあえず「class ClassLoader」が self::$loader に入ります。
ので、もし
・vendor/autoload.phpが2回以上includeされて
・その結果として、2回以上 ComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91::getLoader() がcallされても
その時は
if (null !== self::$loader) {
return self::$loader;
}
があるので「初回のタイミングでnewされたインスタンスがreturnされるだけ」となります。
なお次の行で
spl_autoload_unregister(array('ComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91', 'loadClassLoader'));
とあるので、「spl_autoload_register(array('ComposerAutoloaderInit55b9de9bcb64d89e3a574767b84d6d91', 'loadClassLoader'), true, true);」は、速やかに削除されていきます、というあたりも軽くチェックしておきましょう。
次にいきます。
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
PHP_VERSION_IDとHHVM_VERSIONについては https://www.php.net/manual/ja/reserved.constants.php#reserved.constants.core などを読んでいただきつつ。もう一つが「zend_loader_file_encoded()という関数が定義されていたらその結果を取得」していますので、その部分を軽く見ていきましょう。
(!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded())
ですが、「 短絡評価 」というものがあるので、そのあたりが理解できるとわかりやすいでしょう。
また、上述の「zend_loader_file_encoded()」という関数は、「Zend Guard( http://www.konekto.jp/product/zendguard7.html )」を使っている場合に入っている関数になります。
これはまぁぶっちゃけると「滅多に入っていない」ので、この部分は、本解説では一端「false固定」としておきます。
そうすると
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
は
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!false || !zend_loader_file_encoded());
になって、!falseを書き換えると
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (true || !zend_loader_file_encoded());
となり、短絡評価によって
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (true);
経由で
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
となります。
またHHVM_VERSIONは「Facebookが開発・公開している、HHVMというPHP実行環境」の時に宣言されるものになります。
defined関数については、PHPのマニュアル( http://php.net/manual/ja/function.defined.php )を確認してください。端的には「定数が宣言されているかどうかを確認する」関数ですね。
HHVMも、通常は「使っていない事が多い」と思われるので「使っていない」前提としますと
$useStaticLoader = PHP_VERSION_ID >= 50600 && !false;
になりますので
$useStaticLoader = PHP_VERSION_ID >= 50600 && true;
となり、結果的に
$useStaticLoader = PHP_VERSION_ID >= 50600;
となります。
さて。
PHP_VERSION_IDは「定義済みの定数( http://php.net/manual/ja/reserved.constants.php )」として定義された「バージョンを比較する際に有用な」定数になります。値がintなので、便利ですね。
なので、上述は大雑把には「使っているバージョンがPHP5.6.0以降ならture、そうでなければfalse」となります。
んで、多分、バージョンも5.6以降の方が多いと思って(願って、祈って、懇願して)いますので、上述は一端、trueルートを説明していきます。
trueルートだとすると
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::getInitializer($loader));
なので比較的短くて楽ですね……とはいえまた別ファイルに飛びますが。
飛び先、vendor/composer/autoload_static.php のファイルの、該当のメソッドを見てみましょう。
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$prefixesPsr0;
}, null, ClassLoader::class);
}
このコードも結構色々とやっているので。
少し丁寧に見ていきましょう。
まず
$loader->prefixLengthsPsr4 = ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$prefixesPsr0;
の部分ですが。これは割と簡単で、例えば ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$prefixLengthsPsr4 であれば
public static $prefixLengthsPsr4 = array (
'S' =>
array (
'Slim\\' => 5,
),
'P' =>
array (
'Psr\\Http\\Message\\' => 17,
'Psr\\Container\\' => 14,
),
'I' =>
array (
'Interop\\Container\\' => 18,
),
'F' =>
array (
'FastRoute\\' => 10,
),
);
とあるので、単純に「なんらかのデータ」を入れている、程度になります。
気になるのは。
prefixLengthsPsr4にしてもprefixDirsPsr4にしてもprefixesPsr0にしても
vendor/composer/ClassLoader.php
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
とありまして、端的には「privateプロパティ」なんですよね。
これを解決するために、\Closure::bindを使って「ClassLoader::classの空間の中で関数を実行する」ようにして、データを入れています。
\Closure::bind については、\Closure::bind で少し解説を入れてみました。
普通にビジネスロジック側でこれをやると割とガッツリ嫌われそうな気がするのですが、フレームワークとかcore付近だと、この手の手法はどうしても「やむを得ないケース」があるので、こういった手法は「理解した」上で「節度を持って最低限かつ最小限」使えるようにしていきたいものです。
……とまぁこんな流れで、一端、インスタンスに「データ」を入れてあげています。
if文の中はこれだけですので、抜け出た先、もうちょっと頑張って見ていきましょう。
$loader->register(true);
については
vendor/composer/ClassLoader.php
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
なので、 https://www.php.net/manual/ja/function.spl-autoload-register.php を見れば一通りわかるか、と思いますが。
・第一引数が配列なので「自分自身のloadClass()を呼んでくれ」
・第二引数がtrueなので「autoload関数が登録できなかったら例外で教えてね」
・第三引数が、結果的にtrueなので「この処理はキューの先頭に入れておいてね(優先順位高くしてね)
ってな感じになります。
loadClass()メソッドについては
vendor/composer/ClassLoader.php
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
とあり、端的には「ファイルがあったらincludeしとくよ」といった程度になります。
findFile()メソッドは少し長いので、チャンスがあったら、別稿で詳しく説明をしていきたいと思います。
さて、そろそろ終盤です。
もうちょっとだけ、頑張っていきましょう。
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
$useStaticLoader は前半で出てきましたね。一旦「trueルートで」というお話をしましたので、trueのほうだけ見ていきます。
Composer\Autoload\ComposerStaticInit55b9de9bcb64d89e3a574767b84d6d91::$files; は、そのまま vendor/composer/autoload_static.php に
public static $files = array (
'253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php',
);
とありますので、この配列が返ってきます。
ちなみに別の(実際に動いている)プロジェクトだと、例えば
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
'253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php',
'023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'8a9dc1de0ca7e01f3e08231539562f61' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/functions.php',
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php',
);
や
public static $files = array (
'1d1b89d124cc9cb8219922c9d5569199' => __DIR__ . '/..' . '/hamcrest/hamcrest-php/hamcrest/Hamcrest.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php',
'5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
'e7223560d890eab89cda23685e711e2c' => __DIR__ . '/..' . '/psy/psysh/src/Psy/functions.php',
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'f0906e6318348a765ffb6eb24e0d0938' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
'58571171fd5812e6e447dce228f52f4d' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/helpers.php',
);
といった風に増えている事も多いのですが。
ともあれ、上述のように「配列」が返ってくるので。
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire55b9de9bcb64d89e3a574767b84d6d91($fileIdentifier, $file);
}
からの
function composerRequire55b9de9bcb64d89e3a574767b84d6d91($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
を使って
・__composer_autoload_files変数にkeyがなければ、valueのところのファイルをrequireしておく
という動き、につながっていきます。
個人的には
・なんでglobal変数?
・とはいえまぁ、 globalじゃなくて$GLOBALSを使ってるから、ちょろっとだけ「マシ」だあぁ
とか色々な思いが去来するところではあります。
閑話休題
で、最後に
return $loader;
となって「ClassLoaderクラスのインスタンスをreturn」します。
まぁこのreturn、見ている限りでは「多くの場合、特に使われていない」んですけどね。
なのでちなみに。
$obj = require 'vendor/autoload.php';
var_dump($obj);
exit;
って書くと
object(Composer\Autoload\ClassLoader)#1 (10) {
["prefixLengthsPsr4":"Composer\Autoload\ClassLoader":private]=>
array(4) {
["S"]=>
array(1) {
["Slim\"]=>
int(5)
}
["P"]=>
array(2) {
["Psr\Http\Message\"]=>
int(17)
["Psr\Container\"]=>
int(14)
}
["I"]=>
array(1) {
["Interop\Container\"]=>
int(18)
}
["F"]=>
array(1) {
["FastRoute\"]=>
int(10)
}
}
["prefixDirsPsr4":"Composer\Autoload\ClassLoader":private]=>
array(5) {
["Slim\"]=>
array(1) {
[0]=>
string(58) "/home/gallu/slim_analysis/vendor/composer/../slim/slim/Slim"
}
["Psr\Http\Message\"]=>
array(1) {
[0]=>
string(64) "/home/gallu/slim_analysis/vendor/composer/../psr/http-message/src"
}
["Psr\Container\"]=>
array(1) {
[0]=>
string(61) "/home/gallu/slim_analysis/vendor/composer/../psr/container/src"
}
["Interop\Container\"]=>
array(1) {
[0]=>
string(101) "/home/gallu/slim_analysis/vendor/composer/../container-interop/container-interop/src/Interop/Container"
}
["FastRoute\"]=>
array(1) {
[0]=>
string(64) "/home/gallu/slim_analysis/vendor/composer/../nikic/fast-route/src"
}
}
["fallbackDirsPsr4":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["prefixesPsr0":"Composer\Autoload\ClassLoader":private]=>
array(1) {
["P"]=>
array(1) {
["Pimple"]=>
array(1) {
[0]=>
string(61) "/home/gallu/slim_analysis/vendor/composer/../pimple/pimple/src"
}
}
}
["fallbackDirsPsr0":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["useIncludePath":"Composer\Autoload\ClassLoader":private]=>
bool(false)
["classMap":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["classMapAuthoritative":"Composer\Autoload\ClassLoader":private]=>
bool(false)
["missingClasses":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["apcuPrefix":"Composer\Autoload\ClassLoader":private]=>
NULL
}
って感じの情報を取ることができます。
また
require 'vendor/autoload.php';
var_dump( spl_autoload_functions() );
exit;
で「auto loaderの登録関数」を確認すると
array(1) {
[0]=>
array(2) {
[0]=>
object(Composer\Autoload\ClassLoader)#1 (10) {
["prefixLengthsPsr4":"Composer\Autoload\ClassLoader":private]=>
array(4) {
["S"]=>
array(1) {
["Slim\"]=>
int(5)
}
["P"]=>
array(2) {
["Psr\Http\Message\"]=>
int(17)
["Psr\Container\"]=>
int(14)
}
["I"]=>
array(1) {
["Interop\Container\"]=>
int(18)
}
["F"]=>
array(1) {
["FastRoute\"]=>
int(10)
}
}
["prefixDirsPsr4":"Composer\Autoload\ClassLoader":private]=>
array(5) {
["Slim\"]=>
array(1) {
[0]=>
string(58) "/home/gallu/slim_analysis/vendor/composer/../slim/slim/Slim"
}
["Psr\Http\Message\"]=>
array(1) {
[0]=>
string(64) "/home/gallu/slim_analysis/vendor/composer/../psr/http-message/src"
}
["Psr\Container\"]=>
array(1) {
[0]=>
string(61) "/home/gallu/slim_analysis/vendor/composer/../psr/container/src"
}
["Interop\Container\"]=>
array(1) {
[0]=>
string(101) "/home/gallu/slim_analysis/vendor/composer/../container-interop/container-interop/src/Interop/Container"
}
["FastRoute\"]=>
array(1) {
[0]=>
string(64) "/home/gallu/slim_analysis/vendor/composer/../nikic/fast-route/src"
}
}
["fallbackDirsPsr4":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["prefixesPsr0":"Composer\Autoload\ClassLoader":private]=>
array(1) {
["P"]=>
array(1) {
["Pimple"]=>
array(1) {
[0]=>
string(61) "/home/gallu/slim_analysis/vendor/composer/../pimple/pimple/src"
}
}
}
["fallbackDirsPsr0":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["useIncludePath":"Composer\Autoload\ClassLoader":private]=>
bool(false)
["classMap":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["classMapAuthoritative":"Composer\Autoload\ClassLoader":private]=>
bool(false)
["missingClasses":"Composer\Autoload\ClassLoader":private]=>
array(0) {
}
["apcuPrefix":"Composer\Autoload\ClassLoader":private]=>
NULL
}
[1]=>
string(9) "loadClass"
}
}
って感じの情報を取ることができます。
このようにして、3行(実質1行)の
require 'vendor/autoload.php';
の中では、かくも様々な処理が走っている事になります。
この記事が気に入ったらサポートをしてみませんか?