【メモ】Laravelのオートロードの仕組みについて学ぶ

PHPを初めて触り、Laravelを勉強しているのですが、オートロードという機能を目にして便利だけどなんで機能しているのか全く分からない状態でした。
今回はそんなオートロードについてちゃんと調べました。
最初はLaravelがそういう機能を提供しているのかな？とか思っていたら、いやいや機能を実現するためにファイルを作ったのはComposerだよとなり、実際にComposerによって作成されたファイルを見てみたらPHPで記述されていて、更に見てみたらPHPの標準としてオートロード機能を実装するための関数が用意されていたという話です。

# Laravelにおけるオートロードとは
オートロードを実施しない場合、クラスをインスタンス化したりするときには、クラスが定義されているファイルをrequire等で読み込んでからインスタンス化をすると思います。
オートロードでは名前空間+クラス名と、そのクラスが実際に記述されているファイルとを紐づけます。そうすること、インスタンス化するときなどに名前空間が分かるように記述すればで自動でファイルを読み込んで使用できるようにしてくれます。
何がありがたいかというとrequireの記述を減らせる点がありがたいらしいです。
LaravelではComposerが作成したオートロード機能を実現するためのファイルをrequireで読み込むことでオートロード機能を実現します。この時点ではオートロード実現前だからrequireで読み込んでいるということですね。
この読み込みの後は基本的にはrequire等の記述はなくても動かすことができます。
```php:public/index.phpから抜粋
// オートロードを実現するファイルの読み込み
require __DIR__.'/../vendor/autoload.php';
```


# オートロード機能のファイルは何をもとに作られたのか？
Laravelではpublic/index.phpにて読み込んでいるvendor/autoload.phpというファイルとその中で使われるいくつかのファイル(vendor/composer配下にあります)がComposerによって作られたファイルです。
これらのファイルはcomposer.jsonをもとに作られているようです。Composerでプロジェクトを作るときに必要だったあのファイルです。
composer.jsonにはプロジェクトで使用するファイルの情報がたくさん書かれているので名前空間とファイルの紐づけが可能ということですね。composer.jsonについてもゆくゆくは理解したいところですがPSR4とかPSR0とかが出てきて心が折れたのでまたしばらくしたら調べてみたいです。
ただここで重要なのはComposerが名前空間とファイルの紐づけを行っているということです。
先にネタバレですがrequireをせずともファイルを使えるようにするすごい機能だと思っていましたがオートロードの実装を追っていくと最後はincludeとかrequireをやってました。すごいやつかと思ったら意外と親近感湧いちゃう系です。
ちなみにComposerがオートロードのためのファイルを作成した時点で認識していなかったファイルは紐づけもされないのでオートロードできません。
ファイルを追加したらそれをComposerに教えてあげる必要がありそうです。その方法はまた実際に使うときに調べます。

# オートロードを実装するための関数
クラスとファイルの紐づけができていたとしてもそれだけではオートロードはできません。
PHPにはオートロードを実現するための関数があるのでそちらを使用します。Composerによって自動作成されたファイルもその関数を使っています。以下はPHPのオートロードのドキュメントに記載されている内容です。
>オブジェクト指向アプリケーションを作成する開発者の多くは、 クラス定義毎に一つのPHPソースファイルを作成します。 最大の問題は、各スクリプトの先頭に、必要な読み込みを行う長いリストを 記述する必要があることです(各クラスについて一つ)。
spl_autoload_register() 関数を使うと、 任意の数のオートローダーを登録でき、 クラスやインターフェイスが定義されていなくても自動的に読み込めるようになります。 オートローダーを登録すれば、PHPがエラーで止まる前にクラスをロードする最後の チャンスが与えられます。
クラスに類似した言語構造は、同じやり方でオートロードできます。 これには、クラス、インターフェイス、トレイト、列挙型が含まれます。
https://www.php.net/manual/ja/language.oop5.autoload.php

spl_autoload_register() 関数、これがオートローダーを登録する関数です。オートローダーを登録すれば、使おうとしたクラスがまだ読み込まれていない時に登録したオートローダーが実行されます。
オートローダーとはファイルの読み込みがまだだったときにそのファイルをrequireなりincludeなりする関数のことです。詳しい話は後ほど確認します。


# オートロードの仕組み
ここまでの内容の振り返りを含めてLaravelにおけるオートロードの仕組みを先にまとめます。
①Composerがcomposer.jsonをもとに名前空間付きクラス名とファイル名の紐づけをします。
②spl_autoload_register()でオートローダーを登録します。
③まだ読み込んでいないファイルのクラスを使おうとすると登録したオートローダーが実行されます。
④オートローダーの中ではComposerによって紐づけされた名前空間付きクラス名からファイルを特定して、そのファイルを読み込みます。
⑤オートローダーによってファイルが読み込まれたので問題なくクラスが使用できるようになります。

以上5つのステップです。

LaravelのオートロードではComposerが大量にあるクラスとファイルの紐づけをしてくれている点がすごいです。その仕組みを知るにはPSR4とかPSR0についての理解が必要そうなのですがそこは保留します。今の自分にはちょっと難しいです。。
まあ、あくまでLaravelではオートロードをするのにComposerで紐づけてくれた情報を使っているというだけです。
すごくシンプルなオートロードの実装はspl_autoload_register()でファイルを読込む機能だけの関数を登録するだけで作成できます。この後spl_autoload_register()の使い方と実装例を公式ドキュメントをベースに見ていきます。そのあとにオートロードの実装例も紹介します。

# spl_autoload_register() 関数
```php:spl_autoload_register() 
spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool
```
spl_autoload_registerという名前の関数ですが、splとはThe Standard PHP Libraryの略称で、PHPの標準機能として備わっているライブラリですのでデフォルトで使えます。autoload_registerはその名の通りオートロードを登録する奴という意味です。つまりPHPの標準で備わっている、オートロードを登録する奴です。
引数をひとつずつ見ていきます。

**?callable $callback = null**
spl_autoload_register() は、これまたSPLが提供する__autoloadキューに引数で渡した関数を登録します。__autoloadキューには関数を複数入れることができ、実施するときは入れられた順で実施されます。
デフォルトはnullで、もしnullのまま渡すとまたまたSPLで提供しているspl_autoload()という関数が登録されます。nullで渡すことがあるのかよくわからないので今はspl_autoloadについてはスルーします。

**$throw = true**
spl_autoload_register()が引数で渡された関数を登録できなかったときに例外をスローするかどうかを指定します。
オートロードのドキュメントに下記の記載がありますが、これは関数を登録した後、実際に実行する際は内部でエラーを発生させない方がいいよという意味です。登録できない場合にエラーにするもしないも任意です。
>注意:
複数のオートローダーを複数回登録するために、 spl_autoload_register() を複数回コールしても構いません。 但し、オートロードを行う関数から例外がスローされると、 オートロードのプロセスが中断され、 後続のオートロード関数の実行ができなくなります。 よって、オートロード関数から例外をスローすることは、 全くお勧めできません。
https://www.php.net/manual/ja/language.oop5.autoload.php

**$prepend = false**
これは公式ドキュメントが分かりやすかったです。
>true の場合、spl_autoload_register() はキューの最後に追加するのではなく先頭に追加します。



spl_autoload_registerで大体何をしたいのかが分かったので実際に使用している例を見てみましょう。

# オートロードの実装例

## すごくシンプルな例
すごく簡易的なオートロード機能の実装例がオートロードのドキュメントに載っていたので紹介します。
クラスのファイルが読み込まれていないことが分かるとそのクラス名を使ってincludeします。
たったのこれだけでオートロードが実装できてしまうという衝撃。今日PHP始めた人でもかけそうです。
```php
<?php
//簡易的な例ではクロージャ(無名関数)を登録している
spl_autoload_register(function ($class_name) {
    include $class_name . '.php';
});

$obj  = new MyClass1();
$obj2 = new MyClass2();
?>
```

クロージャーを使えばそのまま引数にできるためとても楽ですが、クロージャをオートローダーとして登録する場合は登録はできても解除はできないのでご認識ください。解除するためにはクロージャ(無名関数)ではなく、ちゃんと名前を付けた関数を渡してあげましょう。解除する場合はどの関数をキューから取り除くのかを指定してあげねばわかりません。
実際わざわざ解除するときってどういうときかあまり分かりませんが、Composerが作ったspl_autoload_registerの実装は名前を指定して引数に渡していたのでこの方法を知っておくと実際のコードが分かるようになります。

※ちなみに解除をする場合はspl_autoload_unregister()関数を使います。名前が似てますがunregisterなので解除します。単純にキューから指定した関数を取り除くもののようです。

https://www.php.net/manual/ja/function.spl-autoload-unregister.php



# 定義済みの関数を登録する例
定義済みの関数を登録するために、先ほどのシンプルなオートロードの実装をちょっと修正します。
```php
<?php
class EasyAutoLoad
{
    public static function ClassLoader($class_name)
    {
        include $class_name . '.php';
    }

    public static function RegistFunctionAndDoAutoLoad()
    {
        spl_autoload_register(array('EasyAutoLoad', 'ClassLoader'));

        $obj  = new MyClass1();
        $obj2 = new MyClass2();
    }
}
```
修正点としてはクラスを作って、その中で関数を定義して、spl_autoload_registerの変数を変更しました。
関数に変更した理由はspl_autoload_registerに関数名を情報として渡せるようにするためです。
callableの引数にはarray('EasyAutoLoad', 'ClassLoader')のように配列でクラス名と関数名を指定して渡すことができます。以下にcallable型に渡す方法が何パターンか記載されています。

https://www.php.net/manual/ja/language.types.callable.php

実際にComposerが作成したコードでもこの方法でオートローダーを登録しています。
該当する部分だけを抜粋しています。
```php:ClassLoader.php
class ClassLoader
{
    // オートローダーとして登録される関数
    public function loadClass($class)
    {
        // Composerが紐づけした情報を使ってクラス名からファイル名を特定する
        if ($file = $this->findFile($class)) {
            // ファイルをincludeする。
            includeFile($file);

            return true;
        }

        return null;
    }

    // spl_autoload_registerを使用する関数。
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }
}

function includeFile($file)
{
    include $file;
}
```
register関数の中でspl_autoload_registerが呼び出されていますが、オートローダーとして自クラスのloadClassという関数を登録しています。loadClassの中では最終的にファイルをincludeする関数の呼び出しがされておりしっかりとオートローダーとしての役目を果たせております。
ここで登録したloadClass関数を使ってLaravelではオートロードを実施しているみたいです。



# 今後の課題
オートロードについて完全理解とまではいきませんが大体何をやっているのかは分かった気がします。
spr4とかspr0(非推奨？)についての理解


参考資料
https://www.php.net/manual/ja/function.spl-autoload-unregister.php
https://www.php.net/manual/ja/language.types.callable.php
https://www.php.net/manual/ja/function.spl-autoload.php
https://www.php.net/manual/ja/function.spl-autoload-register.php