【Composer】オートロードの仕組みを追う

## 初めに

Laravelを使っている方ならこんな疑問を抱いたことがあるんじゃないでしょうか。

**なんで`\App\User`とか`\Illuminate\Hogehoge`って書くだけで`App/User.php`のUserクラスとか`vendor/laravel/framework/src/Illuminate/Hogehoge.php`のHogeHogeクラスが使えるようになってんの？**

通常、外部のクラスを使うときにはrequireなどを使って該当のファイルをインポートしなければならないはずです。

今回はそんな疑問と密接に関わるComposerの**オートロード**について調べていき、提示した疑問を一緒に解決していきましょう。

## 実行環境

* PHP `7.3.4`
* Composer `2.0.11`
* Laravel `6.20.16`

## そもそもオートロードとは

オートロードの仕組みについて追っていく前に、そもそもオートロードって何をしているのでしょうか。

結論を言いますと、単純に`require`や`include`を使ってソースコードを1つ1つインポートしているだけです。

ただし、

```php
require "/path/to/vendor/laravel/src/framework/Illuminate/Foo/Hogehoge1.php";
require "/path/to/vendor/laravel/src/framework/Illuminate/Foo/Hogehoge2.php";
// ...
require "/path/to/vendor/laravel/src/framework/Illuminate/Foo/Hogehoge10.php";
```

のように、べた書きで1つ1つソースコードをインポートしているわけではありません。

ちゃんとPHPの仕組みを利用して自動でソースコードを読み込む機能を構築しています。

PHPでは通常、外部のファイルに記述されているクラスなどを利用するときは、

```php
require "path/to/Foo.php"
require "path/to/Bar.php"
```

というように、読み込みたいファイルを絶対パスもしくは自ファイルからの相対パスで`require`もしくは`include`しなければなりません。

もしもこれを行わないまま外部ファイルに書かれているクラスを呼びだそうとしても、

```php
Uncaught Error: Call to undefined class ...
```

というようにエラーが出力され、クラスを呼び出すことができません。

このようにオートロードをせずとも、自身で`require`することで外部ファイルを読み込むことはできます。

しかし、この方法では扱うファイル数が増えてくるにつれて以下のようなデメリットが生じてきます。

* `require`するためには呼び出したいクラスの定義されているソースコードへのパスをあらかじめ知っていなければならない
* 必要なクラスが5個や6個となってくるとさすがに記述するのが煩わしいし、typoも発生しやすくなる。
* パッケージの更新などによりディレクトリ構造が変更された日には、改めて`require`するソースコードのパスを正しいものに書き直さなければならない

想像しただけで萎えてきます。

人の手でソースコードの所在を管理することが如何に面倒で非効率的であるか分かるでしょうか。

こういった面倒なインポート作業を、Composerのオートロード機能が代わりに担当しています。

そのおかげで、私たちはクラスの所在などを一切気にせずビジネスロジックの開発に注力することができるのですね。

## オートロード処理の起点：vendor/autoload.php

オートロードがなにをしているかが分かったところで、ではどのようにオートロードが行われているをソースコードを追いながら見てみましょう。

ここではLaravelを使っているものだと仮定します。

まずはLaravelアプリのエントリポイントとなる`public/index.php`を見てみます。

```php:public/index.php
<?php

define('LARAVEL_START', microtime(true));

require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);
```

コメントなどを省くと、Laravelによる一連の処理はここに書かれていることがすべてです。Laravelの豊富な機能がこのかなり短いコードを起点に提供されているんですね。

この中の2番目の処理を見てください。

```php:public/index.php
require __DIR__.'/../vendor/autoload.php';
```

名前から見ていかにもオートロードしてそうなファイルを`require`してますね。このファイルを見てみましょう。

```php:vendor/autoload.php
<?php

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitcde23787628405c61112bd11321f024e::getLoader();
```

`ComposerAutoloaderInitcde23787628405c61112bd11321f024e`の部分は人によって異なっているのだと思います。

`public/index.php`に続きかなり短いコード量です。

ここでは`ComposerAutoloaderInitcde23787628405c61112bd11321f024e`という謎のクラスで定義されている`getLoader()`とやらクラスメソッドが実行されているだけです。

`vendor/autoload.php`の行っている処理がこれだけなので、おそらく`::getLoader()`メソッドの中にどうやってオートロード機能を提供しているかの答えがありそうです。

このクラスの実装は一つ手前の処理で読み込まれている、`vendor/composer/autoload_real.php`にありそうです。

では、`autoload_real.php`を見てみましょう。

## オートロード機能の中核：vendor/composer/autoload_real.php

`vendor/autoload.php`で`require_once`されていた`vendor/composer/autoload_real.php`を見てみると、
確かに先ほどの謎のクラス`ComposerAutoloaderInitcde23787628405c61112bd11321f024e`が実装されていました。

```php:vendor/composer/autoload_real.php

// ①
class ComposerAutoloaderInitcde23787628405c61112bd11321f024e
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        require __DIR__ . '/platform_check.php';

        spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
        spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::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\ComposerStaticInitcde23787628405c61112bd11321f024e::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file);
        }

        return $loader;
    }
}

// ②
function composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}

```

このファイルには①で示した`ComposerAutoloaderInitcde23787628405c61112bd11321f024e`クラスと、②で示した`composerRequirecde23787628405c61112bd11321f024e()`関数が定義されているようです。

クラスのほうにはさらに、静的プロパティである`$loader`と静的メソッドである`loadClassLoader()`と`getLoader()`が実装されています。

`vendor/autoload.php`で呼び出されているのは`::getLoader()`の方なのでそちらに注目してみましょう。

## オートロード機能の実態を生成：getLoader()

以下、`getLoader()`部分のみの抜粋です。

```php:vendor/composer/autoload_real.php
public static function getLoader()
{
    if (null !== self::$loader) {  // ①
        return self::$loader;
    }

    require __DIR__ . '/platform_check.php';  // ②

    // ③
    spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
    self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
    spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));

    // ④
    $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
    if ($useStaticLoader) {
        // ⑤
        require __DIR__ . '/autoload_static.php';

        call_user_func(\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::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\ComposerStaticInitcde23787628405c61112bd11321f024e::$files;
    } else {
        $includeFiles = require __DIR__ . '/autoload_files.php';
    }
    foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file);
    }

    return $loader;
}
```

ここから一つ一つ処理を追っているのでとても長くなります。耐えつつ読み進めていただければと思います。

### ①の処理：$loaderの有無を確認

①を見てください。

```php:vendor/composer/autoload_real.php
if (null !== self::$loader) {  // ①
    return self::$loader;
}
```

クラスプロパティ、`self::$loader`が`null`でなければ`$loader`を返すという分岐を行っています。

`vendor/autoload.php`で`getLoader()`が実行されたときにはまだ`$loader`には何も代入されていないので、この条件分岐ではなにもしません。

### ②の処理：動作環境のチェック

②を見てみましょう。

```php:vendor/composer/autoload_real.php
require __DIR__ . '/platform_check.php';  // ②
```

名前から鑑みに、実行環境のチェックを行いそうなファイルが`require`されていますね。

このファイルの処理は本筋からそれるので、コードの記載とコメントによる簡単な説明にとどめておきます。

```php:vendor/composer/platform_check.php.php
<?php

$issues = array(); // 問題があった時にその情報を保存する配列を保存する配列

if (!(PHP_VERSION_ID >= 70205)) { // PHPのバージョンが7.2.5より下だった場合
    $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.';
}

// 上記の問題が保存されていた場合
if ($issues) {
    if (!headers_sent()) {　// まだヘッダが送信されていない場合
        header('HTTP/1.1 500 Internal Server Error');  // ステータスコード500のヘッダを作成
    }
    if (!ini_get('display_errors')) { // php.iniにdisplay_errorsの項目がないか、またはあってもOffであるか
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { // 使用しているPHPのインターフェースがcliもしくはphpdbgか
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);  //STDERRに問題の情報を出力する
        } elseif (!headers_sent()) { // まだヘッダが送信されていない場合
            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);  // 自身の使っているPHPバージョンの情報を省いて出力する
        }
    }

    // エラーを引き起こして、エラーメッセージを通知する。
    trigger_error(
        'Composer detected issues in your platform: ' . implode(' ', $issues),
        E_USER_ERROR
    );
}
```

### ③：spl_autoload_register()で未定義クラス呼び出し時の処理を登録

③の処理は、Composerがオートロード機能を構築する上で重要な関数を使っている部分なので、重点的に解説していきます。

まずはコードの抜粋です

```php:vendor/composer/autoload_real.php
spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));
```

**1行目**

```php:vendor/composer/autoload_real.php
spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
```

この`spl_autoload_register()`という関数は何をしているのでしょうか。

以前私が[投稿した記事](https://qiita.com/anagosan1027/items/64fc8fd0e1aa8ebce2a9)でも解説したのですが、この関数は

**未定義のクラスやインターフェースが読み込まれた際に、実行してほしい処理を登録しておくことができる関数**

と解釈すればおおむね問題ないと思います。

通常、

```php
new HogeHoge();
```

のように呼び出さしたクラスが自ファイルや`require`したファイルに定義されていないものだった場合、

```php
PHP Error:  Class 'Hogehoge' not found in ...
```

とエラーが出力されてその場で処理が終了するのが普通です。

ここで、

```php
spl_autoload_register(function($class) {
    echo "Want to load $class.\n";
    require __DIR__ . "/${class}.php";
})

new HogeHoge();
```

のような例で、`spl_autoload_register()`の第1引数に、未定義クラスが呼び出された時の処理を登録してみましょう。

このソースコードを実行すると、`new HogeHoge()`の部分でエラーが起きる前に、

```bash
Want to load <呼び出したクラス名>.\n
```

と出力されます。即座にエラーが出力されなかったので、確かに未定義クラス呼び出し時の処理が登録できているようです。

そして、`echo`の内容が出力された後、同一ディレクトリ内の`$class.php`（`$class`には未定義クラスのクラス名が自動で代入されます）があればそれを`require`しようと試みます。

この場合ですと、同一ディレクトリ内に`HogeHoge.php`があれば`require`します。

もし`HogeHoge.php`に`HogeHoge`クラスが存在すればそれを呼び出して、`new HogeHoge()`でインスタンスを生成します。

試しに同一ディレクトリ上にHogeHoge.phpという名前のファイルを作成し、その中で`HogeHoge`クラスを定義してください。

その後でnew HogeHoge()の処理があるソースコードを実行してみてください。

先ほどまでは`new HogeHoge()`でエラーが出力されたのに対して、`spl_autoload_register()`を追加した後ではエラーも何も起こらなくなったはずです。

これはつまり、`HogeHoge()`が正しくインスタンス化されたことを意味します。

`sql_autoload_register()`の動きが分かったところで改めてソースコードの1行目を見てみましょう。

```php:vendor/composer/autoload_real.php
spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
```

`spl_autoload_register()`の第1引数には、登録するオートロード関数を配列として指定することもできます。

今回の場合ですと、

`ComposerAutoloaderInitcde23787628405c61112bd11321f024e`クラス（つまり自身のクラスですね）の`loadClassLoader`メソッドをオートロード関数として登録するという処理になります。

この`loadClassLoader()`メソッドは自クラスに実装されていた2つのクラスメソッドのうちのもう1つですね。（1つは`getLoader()`）

第2、第3引数の意味はそれぞれ

<dl>
    <dt>第2引数</dt>
    <dd>第1引数のオートロード関数が登録できなかった場合に例外をthrowするか</dd>
    <dt>第3引数</dt>
    <dd>すでに別のオートロード関数が登録されている場合、それらより優先して第1引数のオートロード関数を実行させるか</dd>
</dl>

となっています。

以上のことから、1行目の処理を説明しますと、

`ComposerAutoloaderInitcde23787628405c61112bd11321f024e::loadClassLoader`をオートロード関数として最優先に登録する。もし登録できなかったら例外を`throw`する。

といった処理になります。

ではこの`loadClassLoader()`メソッドについて見る、その前に次の行の説明をしておいたほうが`loadClassLoader()`メソッドについても理解しやすいと思います。

**2行目**

```php:vendor/composer/autoload_real.php
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
```

`self::$loader`とローカルスコープの`$loader`に`\Composer\Autoload\ClassLoader`クラスのインスタンスを代入しています。

ですが`\Composer\Autoload\ClassLoader`はこの時点では未定義です。

なので、通常ではここでエラーが出力され処理が止まるのですが、先ほど`spl_autoload_register()`でオートロード関数として`loadClassLoader()`を登録しておきましたね。

なので、エラーが出力される前にそのメソッドが実行されます。

では改めて、`loadClassLoader()`メソッドを見てみましょう。

```php:vendor/composer/autoload_real.php
public static function loadClassLoader($class)
{
    if ('Composer\Autoload\ClassLoader' === $class) {
        require __DIR__ . '/ClassLoader.php';
    }
}
```

中身を見てわかるように、やはり`\Composer\Autoload\ClassLoader`の実装ファイルであろう`vendor/composer/ClassLoader.php`を`require`してましたね。

詳しく処理を見てみると、呼び出された未定義クラスが`\Composer\Autoload\ClassLoader`であれば、同一ディレクトリ上の`ClassLoader.php`を`require`する。という処理になっています。

`vendor/composer/ClassLoader.php`について、現在必要な部分のみを見てみましょう。

```php:vendor/composer/ClassLoader.php
namespace Composer\Autoload;

class ClassLoader
{
    private $vendorDir;

    // ~~中略~~

    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
    }

    // ~~後略~~
}
```

やはり、`\Composer\Autoload\ClassLoader`クラスが実装されていましたね。

`__construct()`でパラメータに受け取ったパスを`private $vendorDir`に代入しているようです。

`\Composer\Autoload\ClassLoader`クラスについては今はこの程度の理解で十分です。

改めて2行目の処理を見てみましょう。

（以下、該当部分を再掲）

```php:vendor/composer/autoload_real.php
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
```

`\dirname(\dirname(__FILE__))`は内側から

|      |      |
|----:| ---- |
|`__FILE__`| `/path/to/vendor/composer/autoload_real.php`|
| `\dirname(__FILE__)`|  `/path/to/vendor/composer` |
| `\dirname(\dirname(__FILE__))`|  `/path/to/vendor` |

と、解決されます。

ですので、`new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));`によって、
`$vendorDir`プロパティに`'/path/to/vendor'`が代入された`ClassLoader`インスタンスを`self::$loader`と`$loader`に代入する。

というのが2行目で行っている処理になります。

ここで代入された`\Composer\Autoload\ClassLoader`インスタンスが、Composerのオートロードを実現する上での核となるインスタンスになります。

詳細については後ほど（⑥あたりで）説明します。

**3行目**

```php:vendor/composer/autoload_real.php
spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));
```

これは関数名からなんとなく察せるんじゃないでしょうか。

ここでは、`spl_autoload_register()`の反対、オートロード関数の登録解除を行っています。

具体的には、先ほどの`loadClassLoader()`メソッドをオートロード関数から削除しています。

**③のまとめ**

つまるところ③では、

1. `spl_autoload_register()`を使い、`loadClassLoader()`をオートロード関数として登録。
2. `\Composer\Autoload\ClassLoader`クラスが`loadClassLoader()`により読み込まれ、`vendor`ディレクトリへの絶対パスをパラメータとして`\Composer\Autoload\ClassLoader`クラスのインスタンスを作成、`self::$loader`と`$loader`に代入。
3. `spl_autoload_unregister()`を使い、`loadClassLoader()`をオートロード関数から削除

という処理が行われていることが分かりました。

### ④の処理：マッピング情報の読み込み方法を決定

```php:vendor/composer/autoload_real.php
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
```

④では論理演算式によって、どのような読み込み方法を取るのかを決定しています。

この値によって、この後の⑤が⑤´に分岐するが決まります。

ここも②と同様本筋からそれますので軽い説明程度にとどめておきます。

|条件式|説明|
|:-:|:-:|
|`PHP_VERSION_ID >= 50600`|PHPのバージョンが5.6.0以上であれば`true`|
|`!defined('HHVM_VERSION')`|`HHVM`という`virtual machine`を使用していなければ`true`|
|`(!function_exists('zend_loader_file_encoded')` |`zend guard loader`というランタイムを使っていなければ`true` |
|`!zend_loader_file_encoded()` |`zend guard loader`を使っていても`zend guard`によりエンコードされていなければ`true` |

（`HHVM_VERSION`と`zend_loader_file_encoded`についてはかなりあいまいな説明です。有識者の方がいらっしゃっいましたらコメントしていただけると助かります）

正直ほとんどの方が、`PHP_VERSION_ID >= 50600`くらいにしか結果を左右されないと思います。

ですので、PHPバージョンが5.6.0以上であれば`$useStaticLoader`には`true`が入ると思って差し支えないでしょう。（差し支えありましたらすいません）

### ⑤の処理：マッピング情報を読み込み

ここから先、`$useStaticLoader`の値によって⑤と⑤´に処理が分かれますが、行っていることとしてはどちらとも、クラスとソースコードのマッピング情報を`$loader`にセットしている処理になりますので、そのことを念頭に置いておくと良いかもしれません。

`$useStaticLoader`が`true`の場合はこちらになります。

```php:vendor/composer/autoload_real.php
require __DIR__ . '/autoload_static.php';

call_user_func(\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer($loader));
```

ここではまず、`vendor/composer/autoload_static.php`を`require`しています。

その後に、`\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e`というまた長ったらしい名前をしているクラスのクラスメソッド、`getInitializer()`を実行しているようです。

最後に`getInitializer()`から返されるクロージャ（でしょうか？）を`call_user_func()`を使って実行しています。

おそらく`\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e`クラスの定義も`vendor/composer/autoload_static.php`で行われているでしょうから、そちらを見てみましょう。

このファイルを開くと、人によっては数千行にも及ぶソースコードが表示されると思います。

ですが記述のほとんどが、[クラス名 => 定義されているソースコードへのパス]という連想配列で構成されているはずです。

なので`\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e`
の実態は、以下のように`public`なクラスプロパティが5つと`public`なクラスメソッドが1つだけのクラスです。

```php
<?php

namespace Composer\Autoload;

class ComposerStaticInitcde23787628405c61112bd11321f024e
{
    public static $files = array (
        // ...
    );

    public static $prefixLengthsPsr4 = array (
        // ...
    );

    public static $prefixDirsPsr4 = array (
        // ...
    );

    public static $prefixesPsr0 = array (
        // ...
    );

    public static $classMap = array (
        // ...
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0;
            $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap;

        }, null, ClassLoader::class);
    }
}
```

今回`vendor/composer/autoload_real.php`で呼び出されたのは`::getInitializer()`ですので、そちらを詳しく見てみましょう。

以下、`getInitializer()`の抜粋です。

```php
public static function getInitializer(ClassLoader $loader)
{
    return \Closure::bind(function () use ($loader) {
        $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4;
        $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4;
        $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0;
        $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap;

    }, null, ClassLoader::class);
}
```

やはり、`\Closure::bind()`によってクロージャが返されていました。

ところで、この`\Closure::bind()`は一体何をしているのでしょうか。

（ここでは`\Closure::bind()`について簡単な説明のみにとどめておきます。

`\Closure::bind()`の詳しい説明は[公式マニュアル](https://www.php.net/manual/ja/closure.bind.php)に記載されていますのでご参照ください）

**\Closure::bind()について**

かいつまんで説明すると、`\Closure::bind()`は

**第1引数に指定した関数に対して、第2引数に指定したオブジェクトと第3引数に指定したクラスのスコープを関連付けてクロージャを複製する**

メソッドです。

もう少しわかりやすい例でいうと

**第3引数にクラス名を指定することで、複製されたクロージャはまるでそのクラスに実装されたメソッドであるかのように、そのクラスに定義されているプロパティに対して（`private`であっても）アクセスが可能となります**

また今回の例では関係ないのですが、

**第2引数にインスタンスを渡すことで、クロージャ内で`$this`と記述した部分にそのインスタンスが割り当てられます**

<details><summary>`\Closure::bind()`について具体的な説明</summary><div>

以下のような`Foo`クラスがあります。

```php
class Foo
{
    private static $sFoo = "private static foo\n";
    private $foo = "private foo\n"; 
}
```

このクラスはプロパティを2つ持っていますが両方とも`private`なプロパティなので

```php
echo Foo::$sFoo;  // PHP Fatal error:  Uncaught Error: Cannot access private property Foo::$sFoo in ...
echo (new Foo())->foo;  // PHP Fatal error:  Uncaught Error: Cannot access private property Foo::$foo in ...
```

のように外部からアクセスしようとしても当然エラーが起きます。

そこで、`\Closure::bind()`を使い`private`なプロパティへアクセスできるクロージャを作成してみましょう。

```php
class Foo
{
    private static $sFoo = 'private static foo\n';
    private $foo = 'private foo\n';
}

$cls = \Closure::bind(function (): void {
    echo Foo::$sFoo;
    (new Foo())->foo;
}, null, Foo::class);

$cls();
```

すると、

```bash
private static foo
private foo
```

と`private`なプロパティを出力することができました。

このクロージャはさながら、

```php
class Foo
{
    private static $sFoo = 'private static foo\n';

    private $foo = 'private foo\n';

    public function cls()
    {
        echo Foo::$sFoo;
        (new Foo())->foo;   
    }
}
```

のようなクラスを定義した時の、`cls()`メソッドと同等の振る舞いをしているものだとみなせます。

ただし違いが1つあり、それは**メソッドなら再利用可能だが、`\Closure::bind()`によって複製されたクロージャは他の変数に保存しないと再利用不可になる**という点です。

これは、初期化処理のように最初の1回だけしかその処理が必要じゃない、しかし`private`や`protected`されたプロパティに対して処理を施したい、という場面でこそ有効な特徴になると思います。

では本筋に戻ります。

</div></details>

以上の説明を踏まえて、`\Closure::bind()`が何をやっているのかを見てみましょう。

以下、`::getInitializer()`の再掲です。

```php
public static function getInitializer(ClassLoader $loader)
{
    return \Closure::bind(function () use ($loader) {
        $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4;
        $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4;
        $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0;
        $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap;

    }, null, ClassLoader::class);
}
```

`\Closure::bind()`の第1引数に複製したい無名関数を渡すことは先ほど説明しました。

第2引数は`null`、つまりどのインスタンスともバインドしない静的メソッドとして複製することを示しています。

第3引数では、`class`キーワードを使って`ClassLoader`クラスの完全修飾名を渡しています。

つまりここで複製されるクロージャは、まるで`\Composer\Autoload\ClassLoader`クラスに実装されたメソッドであるかのように`\Composer\Autoload\ClassLoader`クラスのプロパティに対してアクセスが可能となっているクロージャです。

そのことを確認する方法として、試しに第3引数を`ClassLoader::class`から`null`に変更した後

```bash
php vendor/autoload.php
```

をコンソール上で実行してみてください。

```bash
PHP Fatal error:  Uncaught Error: Cannot access private property Composer\Autoload\ClassLoader::$prefixLengthsPsr4
```

というようなエラーが出力されるはずです。どうやら、

```php
$loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4;
```

の部分で`Composer\Autoload\ClassLoader`の`private`なプロパティにアクセスしてしまっているようですね。

`Composer\Autoload\ClassLoader`クラスのプロパティを確認してみましょう。

```php
<?php

namespace Composer\Autoload;

class ClassLoader
{
    private $vendorDir;

    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    private static $registeredLoaders = array();

    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
    }

    // 後略
}
```

このようにすべて`private`で定義されています。

ですので先ほどのクロージャからではクラス外からのアクセスになってしまうので、`Cannot access private property`と怒られてしまったんですね。

以上で、`\Closure::bind()`自体の説明は終わりです。

`vendor/composer/autoload_static.php`の変更した部分を`ClassLoader::class`に戻して、

```bash
php vendor/autoload.php
```

がエラーを出力することなく完了したことを確認したら次に進みましょう。

**`\Closure:bind()`に渡した無名関数**

では、`\Closure:bind()`が複製する対象の無名関数を見てみましょう。

以下、無名関数の部分のみの抜粋です。

```php
function () use ($loader) {
    $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4;
    $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4;
    $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0;
    $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap;
}
```

無名関数の中で外部の変数を使う場合は、予め`use`を使って内部で使う変数を渡してあげる必要があります。

今回では、`$loader`が外部の変数ですので`use`に含めておきます。

先ほど示した、`\Composer\Autoload\ClassLoader`クラスのプロパティにこの4つのプロパティもあります。

これらのプロパティに対してそれぞれ、`ComposerStaticInitcde23787628405c61112bd11321f024e`クラス（つまり自クラスですね）で定義されている同名のクラスプロパティを代入しています。

つまりこの無名関数の中で、オートローディングに必要なマッピング情報を`$loader`に渡しているのです。

以上でこの無名関数に関する説明は終了です。

**まとめ**

さて、⑤の処理の流れを総括すると

1. `\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e`が呼び出せるように、`require`で `vendor/composer/autoload_static.php`を読み込み

2. `ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer($loader)`の戻り値を`call_user_func()`の引数に指定

3. `ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer()`では、渡ってきた`$loader`を無名関数の処理に組み込み、適切なクラススコープを付与したクロージャを返す。

4. `call_user_func()`は返ってきた無名関数を実行して、`$loader`のプライベートなプロパティにマッピング情報を渡して読み込ませる。

といったことが行われていることが分かりました。

### ⑤´の処理：⑤と異なる方法でマッピング情報を読み込み

こちらは`$useStaticLoader`が`false`の場合の処理になります。

```php:vendor/composer/autoload_real.php
$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);
}
```

大きく分けて3つの処理に分けることができますが、どれも流れは同じです。

まずは`vendor/composer/autoload_namespaces.php`を見てみましょう。

```php:vendor/composer/autoload_namespaces.php
<?php

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Mockery' => array($vendorDir . '/mockery/mockery/library'),
    'Highlight\\' => array($vendorDir . '/scrivo/highlight.php'),
    'HighlightUtilities\\' => array($vendorDir . '/scrivo/highlight.php'),
);

```

このように、[名前空間 => 対応させるファイルパスの入った配列]という連想配列を`$map`に返しています。

これを、`foreach`を使って`$namespace`と`$path`に切り分けながら1つずつ`$loader->set()`にかけているようです。

名前から想像できると思いますが一応、中身を見てみましょう。

```php:vendor/composer/ClassLoader.php
public function set($prefix, $paths)
{
    if (!$prefix) {
        $this->fallbackDirsPsr0 = (array) $paths;
    } else {
        $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
    }
}
```

このメソッドでは、

* `$prefix`つまり`$map`のキーが空文字だったら`fallbackDirsPsr0`プロパティにパスを追加

* `$prefix`があれば、`prefixesPsr0`プロパティ内の`$prefix[0]`つまり頭文字に対応する配列`>prefixesPsr0[$prefix[0]]`に`[$prefix => array($path)]`の形で追加

している処理を行っていると思われます。

同様に`vendor/comoser/autoload_psr4.php`を見てみると、

```php:vendor/comoser/autoload_psr4.php
<?php

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'),
    // ...
);
```

と、`autoload_namespaces.php`と同様の構造でマッピング情報を`$map`に返します。

そしてやはり先ほどと同様、`foreach`を使って`$namespace`と`$path`に切り分けながら1つずつ`$loader->setPsr4()`にかけているようです。

こちらも実装を見てみましょう。

```php:vendor/composer/ClassLoader.php
public function setPsr4($prefix, $paths)
{
    if (!$prefix) {
        $this->fallbackDirsPsr4 = (array) $paths;
    } else {
        $length = strlen($prefix);
        if ('\\' !== $prefix[$length - 1]) {
            throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
        }
        $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
        $this->prefixDirsPsr4[$prefix] = (array) $paths;
    }
}
```

流れを追うと、

* `$prefix`つまり`$map`が空文字だったら`fallbackDirsPsr0`プロパティにパスを追加

* `$prefix`の最後の文字が`'\\'`でなければ例外を投げる。

* そのどちらでもなければ

    * 先ほどと同様の指定方法で`prefixLengthsPsr4[$prefix[0]]`に`[$prefix => $length]`を保存

    * `prefixDirsPsr4`プロパティに`[$prefix => array($paths)]`の形で保存

という処理が行われているようです。

これまた同様に、`vendor/composer/autoload_classMap.php`を見てみましょう。

```php:vendor/composer/autoload_classMap.php
<?php

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'App\\Console\\Kernel' => $baseDir . '/app/Console/Kernel.php',
    'App\\Exceptions\\Handler' => $baseDir . '/app/Exceptions/Handler.php',
    // ...
);
```

先ほど2つと同様に連想配列の形式です。ただし、値には配列ではなくて文字列を指定しているようです。

このマップ情報を`$classMap`に返し、`$classMap`が空ではなければ`$loader->addClassMap()`に渡しているようです。

以下、`addClassMap()`の実装です。

```php

public function addClassMap(array $classMap)
{
    if ($this->classMap) {
        $this->classMap = array_merge($this->classMap, $classMap);
    } else {
        $this->classMap = $classMap;
    }
}
```

これは見たまんまの処理ですね。

* 既に`classMap`プロパティに何かあれば、渡された`$classMap`を`classMap`プロパティに統合

* `classMap`プロパティが空なら、`$classMap`をそのまま`classMap`プロパティに代入

となります。

以上3つの読み込み処理を経て、⑤´でもマッピング情報の読み込みが行われています。

### ⑥の処理：オートローダーの登録

⑥の処理を見てみましょう。

```php:vendor/composer/autoload_real.php
$loader->register(true);
```

おそらく、メソッド名から鑑みてオートロードを行う上で重要な処理を担っているものだと思われます。

それでは`register()`の定義を見てみましょう。

`vendor/composer/ClassLoader.php`を探してみますと確かに`ClassLoader`クラスのメソッドとして定義されていました。

```php:vendor/composer/ClassLoader.php
public function register($prepend = false)
{
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);

    if (null === $this->vendorDir) {
        //no-op
    } elseif ($prepend) {
        self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
    } else {
        unset(self::$registeredLoaders[$this->vendorDir]);
        self::$registeredLoaders[$this->vendorDir] = $this;
    }
}
```

処理を追ってみます。

復習になりますが、`spl_autoload_register()`は未定義のクラス、インターフェースが呼び出されたときに実行されるオートロード関数を登録しておけるPHPの関数でしたね。

ここでは、オートロード関数として**自インスタンスの`loadClass`メソッドをオートロード関数として実行順を最優先で登録**しています。

登録したオートロード関数が何をするのかは後で詳しく見るとして、続けて`if`文の処理を見てみましょう。

記述されている条件分岐は以下のようになります。

* `$this->vendorDir`が`null`、つまりインスタンス生成時にパスを渡さしていなかった場合何もしない

* `$this->vendorDir`が設定されていて、かつ`register()`の引数`$prepend`に`true`を渡していた場合、`[$this->vendorDir => $this]`という配列を順番的に最初にして`self::$registeredLoaders`に追加。

* `$prepend`が`false`なら、`self::$registeredLoaders`から`$this->vendorDir`をキーとする要素を削除した後、順番的に一番後ろに`[$this->vendorDir => $this]`という配列を追加。

どうやら、`if`文では`self::$registeredLoaders`というクラスプロパティに、

「この`vendorDir`のパスの時は、このローダを使う」という情報を`[$this->vendorDir => $this]`という形式の配列で蓄積しているようです。

`register()`メソッドの行っていることはこれで以上です。

では先ほどオートロード関数として登録した`loadClass`メソッドの実装を見てみましょう。

```php:vendor/composer/ClassLoader.php
public function loadClass($class)
{
    if ($file = $this->findFile($class)) {
        includeFile($file);

        return true;
    }
}
```

オートロード関数には実行時に、引数へ呼び出したパラメータが渡されることは既に説明致しました。

処理を予測してみると、

`$this->findFile($class)`によってクラスが定義されているファイルの所在を探し出し、見つかれば`includeFile($file)`でそれを`include`する。

といったところでしょうか。

実際に、`includeFile()`関数は`vendor/composer/ClassLoader.php`の一番下で定義されています。

```php:vendor/composer/ClassLoader.php
function includeFile($file)
{
    include $file;
}
```

純粋に渡されたファイルパスを`include`するだけの関数のようです。

ですので`loadClass()`メソッドの肝はどうやら`$this->findFile($class)`にありそうです。

ではその実装を見てみましょう。

#### findFile()：マッピング情報の検索

```php:vendor/composer/ClassLoader.php
 public function findFile($class)
{
    // class map lookup
    if (isset($this->classMap[$class])) {  // ①
        return $this->classMap[$class];
    }
    if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {  // ②
        return false;
    }
    if (null !== $this->apcuPrefix) { // ③
        $file = apcu_fetch($this->apcuPrefix.$class, $hit);
        if ($hit) {
            return $file;
        }
    }

    $file = $this->findFileWithExtension($class, '.php'); // ④

    // Search for Hack files if we are running on HHVM
    if (false === $file && defined('HHVM_VERSION')) { // ⑤
        $file = $this->findFileWithExtension($class, '.hh');
    }

    if (null !== $this->apcuPrefix) { // ⑥
        apcu_add($this->apcuPrefix.$class, $file);
    }

    if (false === $file) { // ⑦
        // Remember that this class does not exist.
        $this->missingClasses[$class] = true;
    }

    return $file;
}
```

少々長いです。前提として、`$class`には先ほどの`loadClass`メソッドと同じものが渡されています。

では割り振った番号ごとに処理を追ってみます。

**①**

```php:vendor/composer/ClassLoader.php
if (isset($this->classMap[$class])) {  // ①
    return $this->classMap[$class];
}
```

`$this->classMap`に`$class`のキーが見つかればその値を返します。

**②**

```php:vendor/composer/ClassLoader.php
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {  // ②
    return false;
}
```

`$this->classMapAuthoritative`が`true`もしくは`$this->missingClasses`に`$class`のキーが見つかれば、見つからなかったという意味の`false`を返す。

（`$this->classMapAuthoritative`は`prefix`や`fallback`のディレクトリ検索を禁止するかのプロパティでしょうか、デフォルトでは`false`なので禁止しません）

**③**

```php:vendor/composer/ClassLoader.php
if (null !== $this->apcuPrefix) { // ③
    $file = apcu_fetch($this->apcuPrefix.$class, $hit);
    if ($hit) {
        return $file;
    }
}
```

`$this->apcuPrefix`が`null`でなければ、APCuというPHPの拡張機能（？）が提供する`apcu_fetch()`という関数を使ってキャッシュに`$this->apcuPrefix.$class`というキーがないか検索、ヒットしたらその検索結果をリターンします。

デフォルトでは`null`なのでここの部分はスキップされます。

（この部分に関してはかなりあいまいな説明となっています、見識のある方がおられましたらご指摘していただけると助かります）

参考：[PHP: apcu_fetch - Manual](https://www.php.net/manual/ja/function.apcu-fetch.php)

**④**

```php:vendor/composer/ClassLoader.php
$file = $this->findFileWithExtension($class, '.php'); // ④
```

`$this->findFileWithExtension()`というメソッドを使ってファイルを探しているようです。

かいつまんで説明すると、`findFileWithExtensiton()`メソッドは`$this->prefixDirsPsr4`、`$this->prefixesPsr0`から、`$class`に対応するファイルパスを検索し、見つかればそれを返すメソッドになります。

このメソッドの処理はコード量が長いので折りたたんで解説します。

<details><summary>findFileWithExtension()の解説</summary><div>

このメソッドでは、渡された文字列を地道に解析して`$this->prefixDirsPsr4`、`$this->prefixesPsr0`からファイルパスを取り出しています。

1つずつ解説するととても量が多くなるのでコメントに付記する形で説明を行います。

```php:vendor/composer/ClassLoader.php

private function findFileWithExtension($class, $ext)  
{
    // 以下$class = Illuminate\\Support\\Facades\\Auth として実行
    
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
    // $logicalPathPsr4 = Illuminate/Support/Facades/Auth.php

    $first = $class[0];  // $first = I
    if (isset($this->prefixLengthsPsr4[$first])) {
        $subPath = $class;  // $subPath = Illuminate\\Support\\Facades\\Auth

        
        // $subPathから"\\"のうち最後のものの位置を$lastPosに代入。無ければfalseを返す
        while (false !== $lastPos = strrpos($subPath, '\\')) {
            /*
            $subPathがIlluminate\\Support\\Facades\\Authなら
            Illuminate\\Support\\Facadesに置き換え
            */
            $subPath = substr($subPath, 0, $lastPos);
            $search = $subPath . '\\';

            /*
            $searchが$this->prefixDirsPsr4にあればif文以下を実行
            この場合であれば$subPath = Illuminate, $search = Illuminate\\, $lastPos = 10 の時にtrueとなる
            */
            if (isset($this->prefixDirsPsr4[$search])) {　　

                /*
                substr()の結果がSupport/Facades/Authなので
                $pathEnd = /Support/Facades/Auth.php
                */
                $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);

                /*
                $searchに対応するファイルパスを$dirに取得。
                この場合であれば $dir = /path/to/vendor/composer/../laravel/framework/src/Illuminate
                */
                foreach ($this->prefixDirsPsr4[$search] as $dir) {
                    
                    // $file =  /path/to/vendor/composer/../laravel/framework/src/Illuminate/Support/Facades/Auth.php
                    if (file_exists($file = $dir . $pathEnd)) { // $fileの位置にファイルがあればtrue
                        return $file; // $fileのパスを返す
                    }
                }
            }
        }
    }

    // $this->fallbackDirsPsr4があればループを実行
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }

    // 以下、$class = Mockery, $logicalPathPsr4 = Mockery.php, $first = M として実行 

    // $classに"\\"があれば$posに最後のそれの位置を代入。無ければfalseを返す
    if (false !== $pos = strrpos($class, '\\')) { 

        // ここでは$class =  Hightlight\\Hogehoge, $$logicalPathPsr4 = Hightlight/Hogehoge.php  として実行

        // $logicalPathPsr0 = Hightlight/Hogehoge.php
        $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
            . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
    } else {
        // $logicalPathPsr0 = Mockery.php
        $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
    }

    if (isset($this->prefixesPsr0[$first])) { // $this->prefixesPsr0["M"] があればif文以下を実行

        // $this->prefixesPsr0["M"]を$prefixと$dirsに切り分けてループ
        foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {

            // ここでは$prefix = Mockery として実行
            
            // $classに"Mockery"があり、その最初の位置が0番目ならif文以下を実行
            if (0 === strpos($class, $prefix)) {

                // $dirsを1つずつループ
                foreach ($dirs as $dir) {

                    // ここでは$dir = /path/to/vendor/composer/../mockery/mockery/library として実行
                    
                    /*
                    $file = /path/to/vendor/composer/../mockery/mockery/library/Mockery.php
                    $fileがあれば$fileを返す
                    */
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                        return $file;
                    }
                }
            }
        }
    }
    
    // $this->fallbackDirsPsr0があればループを実行
    foreach ($this->fallbackDirsPsr0 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
            return $file;
        }
    }

    // $this->useIncludePathがtrueで、$logicalPathPsr0のファイルパスが存在すれば$fileを返す
    if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
        return $file;
    }

    // ここまで来たら何も見つからなかったということでfalseを返す
    return false;
}
```

</div></details>

**⑤**

```php:vendor/composer/ClassLoader.php
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) { // ⑤
    $file = $this->findFileWithExtension($class, '.hh');
}
```

コメントアウトされた文章にもあるように`HHVM`が起動している場合に限り、`$file`が`findFileWithExtension()`によって見つからなかったときに、拡張子を`.hh`に変えて再び`findFileWithExtension()`を実行し、ファイルパスを取得しようと試みています。

**⑥**

この時点でファイルパスの検索結果は`$file`に保存されています。
ですので後の処理は次回以降の処理を省くためのものがほとんどです。

```php:vendor/composer/ClassLoader.php
if (null !== $this->apcuPrefix) { // ⑥
    apcu_add($this->apcuPrefix.$class, $file);
}
```

`$this->apcuPrefix`が`null`でなければ、APCuというPHPの拡張機能（？）が提供する`apcu_add()`という関数を使ってキャッシュに、`$this->apcuPrefix.$class`というキーで検索結果をキャッシュに追加しています。

デフォルトでは`null`なのでここの部分はスキップされます。

（この部分に関してもかなりあいまいな説明となっています、見識のある方がおられましたらご指摘していただけると助かります）

*⑦*

```php:vendor/composer/ClassLoader.php
if (false === $file) { // ⑦
    // Remember that this class does not exist.
    $this->missingClasses[$class] = true;
}

return $file;
```

検索の結果何も見つからなかった場合、次回以降の検索処理を省略するために`$this->missinglasses`に`[$class => true]`という形式の配列を追加します。

これによって、次回以降同じクラス名を呼び出した際に①から⑥まで処理を経なくとも②の時点で該当ファイルがないという結果を返すことができます。

以上の処理を経て`findFile()`メソッドは呼び出しクラスに対するファイルパス情報を検索しているのですね。

### ⑦の処理：クラスファイル以外のソースコードを読み込む

いよいよ最後の処理です。ここでは、クラスファイル以外のもの、例えば起動処理をまとめただけのファイルでしたり、ヘルパ関数をまとめたファイルなどをハッシュ値と紐づけて読み込ませる処理を行っています。

```php:vendor/composer/autoload_real.php
if ($useStaticLoader) {
    $includeFiles = Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::$files;
} else {
    $includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
    composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file);
}

return $loader;
```

では処理を追ってみましょう。

まず、`$useStaticLoader`の値によって処理が分岐していますがどちらとも`$includeFiles`に`[ハッシュ値 => ファイルパス]`の構造をした配列を代入しています。

`Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::$files`プロパティにも、`vendor/composer/autoload_files.php`の戻り値にも同様の配列が記載されていると思います。

この配列の要素をハッシュ値`$fileIdentifier`とファイルパス`$file`に切り分けて1つずつ、`composerRequirecde23787628405c61112bd11321f024e()`という関数にかけているようです。

この`composerRequirecde23787628405c61112bd11321f024e()`関数は`vendor/composer/autoload_real.php`の一番下、クラスの外に定義されています。

実装を見てみましょう。

```php:vendor/composer/autoload_real.php

function composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
```

この関数によって、クラスファイル以外のソースコードが読み込まれているようです。

処理を追うと、

1. `$GLOBALS['__composer_autoload_files'][$fileIdentifier]`が空でないか確認

2. 空であればそのハッシュ値に対応するファイルがまだ読み込まれていないので`require`する

3. `$fileIdentifier`に対応するファイルを読み込んだことを記憶しておくために、
`$GLOBALS['__composer_autoload_files'][$fileIdentifier]`に`true`を代入。

といった流れになっています。

この処理が終わると、`getLoader()`は`$loader`をリターンして処理を終了します。

### getLoader()の処理まとめ

とても長くなってしまいましたが以上で`getLoader()`の説明は終わりになります。

最後に各番号の処理を簡単に説明します。

1. `self::loader`がすでにあればそれを返す。

2. 実行環境を確認。

3. コンストラクタの引数に`/path/to/vendor`を指定して`\Composer\Autoload\ClassLoader`クラスをインスタンス化、それを`self::$loader`と`$loader`に代入。

4. `useStaticLoader`をするか決定。

5. マッピング情報を`$loader`に読み込ませる。

6. 実際にオートロード処理を登録。これ以降の未定義クラスの呼び出しはマッピング情報をもとに解決される。

7. クラスファイル以外のソースコードをハッシュ値と対応させて読み込む。その後`$loader`をリターンする

このような処理の流れで、オートロード機能の構築が行われているということがわかりましたね。

この`::getLoader()`によって生成された`$loader`は呼び出し元である`vendor/autoload.php`に返ってきます。

```php
return ComposerAutoloaderInitcde23787628405c61112bd11321f024e::getLoader();
```

返ってきた`$loader`を`vendor/autoload.php`は更にリターンしています。なのでもし外部でローダーを使う処理を実装したい場合は

```php
$loader = require "/path/to/vendor/autoload.php";
```

とすれば`::getLoader()`によって生成されたローダーを使うことができます。

今回起点としたLaravelの`index.php`では

```php:public/index.php
<?php
// ...

require __DIR__.'/../vendor/autoload.php';

// ...
```

のようにローダーの保存は行わず、`::getLoader()`を介してオートロード関数の登録だけを行っています。

保存を行わなかった場合でもクラスプロパティとしてローダーは保存されているので、未定義のクラスが呼び出された場合でもそちらに保存されているローダーの`loadClass`メソッドを通してオートロード処理が行われている、といったところでしょうか。

## 処理の流れまとめ

では、最後に今まで説明してきたことを順序だてて簡潔に追ってみましょう。

1. `public/index.php`で`vendor/autoload.php`が`require`される。

2. `vendor/autoload.php`で`vendor/composer/autoload_real.php`が`require`され、そこに定義されている`ComposerAutoloaderInitcde23787628405c61112bd11321f024e::getLoader()`を実行。

3. `::getLoader()`の処理の中でオートロード機能の実体となる`\Composer\Autoload\ClassLoader`インスタンスを生成し、自クラスの静的プロパティとして保存。

4. そのインスタンスに対してマッピング情報の読み込み、オートロード処理の登録、そしてクラス以外のソースコードに関しても読み込み、といった処理を行う。

このような流れでComposerによるファイルのオートロード処理が行われているのだということが分かりました。

## 終わりに

非常に長い説明になってしまったことをお詫びしたいと思います。

ですが、このように処理を1つ1つ丁寧に追っていったことでComposerのオートロード機能についてはほとんどぬけのない理解が出来たのではないでしょうか。

以前私のほうでも投稿しました記事でも述べたことがあるのですが、普段何気なく使っている機能について一度は深くまで追ってみるという経験をするのは、非常に力がつきます。

現に、この機能を調べるまで知りもしなかった情報や仕様について理解する機会を得られました。

月並みな締めになってしまいますが、皆さんも一度はこのように外部パッケージが提供している仕組みについて、1から深堀りして追ってみる経験をしてみてはいかがでしょうか。