初めに
/public/index.php
を起点にLaravelの処理を追っていたところ、vendor/composer/autoload_real.php
の中にspl_autoload_register()
という何やらよくわからない関数が登場したので、それの説明をする解説記事となります。
動作環境
- PHP
7.4.3
- Composer
2.0.11
該当の関数が登場したソースコード
vendor/composer/autoload_real.php
のgetLoader()
メソッドの中にそれはありました。
class ComposerAutoloaderInitcde23787628405c61112bd11321f024e
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
// ~~中略~~
spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));
// ~~後略~~
}
}
では、このspl_autoload_register()
がなにをしているのかという謎を解決してみましょう。
結論
先に結論を申し上げておきますと、spl_autoload_register()
は、
未定義のクラスやインターフェースが読み込まれたときに実行してほしい処理を登録しておくことができる関数
と、私は解釈いたしました。
つまり、
呼び出したクラス、インターフェースが、自ファイルやrequire
されたものの中に定義されていなかった場合に、クラスのオートロード処理といったいわばセーフティネットのようなものを登録できる関数
と認識すればわかりやすいのではないでしょうか。
以下、大変参考にさせていただきました記事になります。
- PHP: クラスのオートローディング - Manual
- 【PHP】spl_autoload_registerとオートロード | とものブログ
- public/index.php の最初の三行(vendor/autoload.php の挙動)|gallu|note
具体例
サンプルとして同一ディレクトリ上に、Foo.php
とexecute.php
があったとします。
class Foo
{
public function test()
{
echo 'test in Foo class';
}
}
$foo = new Foo() // PHP Error: Class 'Foo' not found in ...
$foo->test();
execute.php
でのFoo
クラスの呼び出しはrequire
も何もされていないので、インスタンス化の時点で当然例外が出力されます。
ではspl_autoload_register()
を使ってFoo
クラスをオートロードしてみたいと思います。
execute.php
にspl_autoload_register()
を追加しました。
<?php
spl_autoload_register(function($class) { // オートロード処理を登録
echo "Want to load $class.\n";
require __DIR__ . "/${class}.php";
});
$foo = new Foo();
$foo->test();
spl_autoload_register()
のexecute.php
を実行してみるとどうでしょう。
Want to load Foo.
test in Foo class
とrequire ./Foo.php
という記述をしていないのにも関わらず、execute.php
でFoo
クラスオブジェクトによるtest()
メソッドが実行されていることが分かります。
以下、解説です。
spl_autoload_register()の使い方
マニュアルによるとspl_autoload_register()
には3つ引数を指定できるようです。
spl_autoload_register ( callable $autoload_function = ? , bool $throw = true , bool $prepend = false ) : bool
autoload_function
登録したい autoload 関数。 パラメータが指定されなかった場合は、デフォルト実装である spl_autoload() が登録されます。
throw
このパラメータは、 spl_autoload_register() が autoload_function を登録できなかったときに例外をスローするかどうかを指定します。
prepend
true の場合、spl_autoload_register() はキューの最後に追加するのではなく先頭に追加します。
かいつまんで説明すると、
-
autoload_function
には登録したいオートローディング処理をcallable
型で指定 -
throw
は引用した文章の通り -
prepend
には登録したコールバックを最優先に実行するかをbool
型で指定
といったように理解すればいいでしょうか。(callable
型についてはこちらを参照してください)
このautoload_function
に指定したコールバック関数には、引数として呼び出そうとした未定義のクラス名やインターフェース名が文字列として渡されます。
この渡される文字列を用いて、動的にrequire
などのファイル読み込み処理を記述しておける、というのがautoload_function
の意義でしょう。
spl_autoload_register()
の呼び出し方法が分かったところで、execute.php
の処理を1つ1つ見てみましょう。
サンプルコードの解説
以下、再掲です。
<?php
spl_autoload_register(function($class) {
echo "Want to load $class.\n";
require __DIR__ . "/${class}.php";
});
$foo = new Foo();
$foo->test();
まずspl_autoload_register()
に渡した無名関数には、
-
"Want to load $class.\n"
という文字列を出力 -
$class
に渡されたクラス名をもとに、同一ディレクトリ内の${class}.php
を読み込む
という処理が記述されています。
なので、"Want to load $class.\n"
という文字列が出力されればspl_autoload_register()
によるオートロードが働いているということが分かりますね。
spl_autoload_register()
によりオートロード処理の登録が完了したところで、次の処理を見てみましょう。
$foo = new Foo();
./execute.php
では未定義のFoo
クラスが呼び出されました。
通常でしたらそのまま例外が出力されるのですが、未定義クラスが呼び出されたことをトリガーとして、spl_autoload_register()
に登録した処理が実行されます。
今回の例でいうと、無名関数の引数$class
には"Foo"
が渡され、
-
"Want to load $class.\n"
が出力され - 同一ディレクトリ内の
./Foo.php
を読み込む
という処理が実行されます。
このオートローディング処理を経て、無事./Foo.php
を読み込むことができました。
なので、$foo = new Foo()
で例外が出力されることはなく、そして$foo->test()
も例外を出すことなく正しい出力がなされるのです。
vendor/composer/autoload_real.php内のgetLoader()の解説
spl_autoload_register()
の動作が分かったところで、本記事の趣旨とは外れますがvendor/composer/autoload_real.php
のgetLoader()
のうちspl_autoload_register()
が絡んでくる部分を解説してみたいと思います。
以下、再掲です。
class ComposerAutoloaderInitcde23787628405c61112bd11321f024e
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
// ~~中略~~
spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));
// ~~後略~~
}
}
getLoader()
メソッドを見てください。こちらでspl_autoload_register()
が呼び出されています。
第1引数には
array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader')
という配列が渡されています。
先ほどautoload_function
には登録したいオートローディング処理をcallable
型で指定すると述べましたが、callable
型として配列を渡すことも可能になっております。
callable
型の引数として配列を渡した場合、
- 0番目にはクラスオブジェクト、またはクラス名の文字列
- 1番目にはそのクラスで定義されているメソッド名の文字列
と指定することで、0番目のクラスに定義されている1番目のメソッドをコールバックとして指定するといったことができます。
参考:PHP: コールバック / Callable - Manual
これを踏まえて該当のspl_autoload_register()
を見てみましょう。
spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
配列の0番目に"ComposerAutoloaderInitcde23787628405c61112bd11321f024e"
(つまり自身のクラスですね)が、1番目に"loadClassLoader"
が渡されています。
ですので、この呼び出しでComposerAutoloaderInitcde23787628405c61112bd11321f024e::loadClassLoader
メソッドがオートローディング処理として登録されることになります。
次の行を見てみると、さっそく未定義のクラス\Composer\Autoload\ClassLoader
が呼び出されています。
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
なので、先ほど指定したばかりのオートロードが実行されます。つまり、自クラスのloadClassLoader()
メソッドが実行されます。
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
先ほどのexecute.php
と似たような処理ですね。言葉で説明すると、
呼び出しクラス名が'Composer\Autoload\ClassLoader'
と一致するなら、vendor/composer/autoload_real.php
と同一ディレクトリにあるClassLoader.php
をrequire
する
となります。
そして今回、new
するクラスがまさに\Composer\Autoload\ClassLoader
クラスであるので、vendor/composer/ClassLoader.php
がオートロードされて、
無事にself::$loader
とローカルスコープ上の$loader
に\Composer\Autoload\ClassLoader
クラスのインスタンスが代入されます。
最後に後処理として、先ほど登録したオートロード処理を
spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'))
によって解除したというところで、今回の解説は以上になります。
終わりに
composer
を使っている方ならオートロードという仕組みが備わっていることを一度は耳にしたことがあると思います。
ですが、その仕組みがどのように提供されているのか詳しく知っている人はあまり多くないと思います。
そういった、普段であれば考えなくていい、ツールが裏側で担っている処理がどのように構築されているかを追ってみるのは私の中でいい経験になりました。
皆さんも一度、いつも使っているツールがどのような処理の上に提供されているのか、1から紐解いてみてはいかがでしょうか。そのツールに対して普段とはまた違った知見を得られるかもしれません。