初めに
/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から紐解いてみてはいかがでしょうか。そのツールに対して普段とはまた違った知見を得られるかもしれません。