Help us understand the problem. What is going on with this article?

初心者がLaravelのindex.phpを読み解いてみた(オートロード編)

動機

パーフェクトPHPの7章、8章を読んでみて、じゃあLaravelはどうなってるんだろうかと気になったので調べてみました。

環境

Laravel 8.x
PHP 7.3

結論

require __DIR__.'/../vendor/autoload.php';の1行で、クラスのオートロード設定、組み込み関数の宣言などを実行している。

index.php

全体としては以下の通りです。非常に簡潔ですが、かなり内容は重いです。今回はrequire __DIR__.'/../vendor/autoload.php';までみていきたいと思います。

public/index.php
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;

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

if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
    require __DIR__.'/../storage/framework/maintenance.php';
}

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

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

$kernel = $app->make(Kernel::class);

$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

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

名前空間の利用

こちらは問題ないかと思います。詳細は、名前空間の利用に関するドキュメントをご参照ください。

public/index.php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;

メンテナンスモード

メンテナンスモードの場合は、storage/framework/maintenance.phpが生成され、エラーレスポンス503を返すようになります。
通常時はファイルが存在しないのでfalseになります。メンテナンスモードの詳細はLaravelのドキュメントをご参照ください。

file_exists:引数のファイルが存在するかどうかを判定する関数。
__DIR__:ファイルのディレクトリを返す。

public/index.php
if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
    require __DIR__.'/../storage/framework/maintenance.php';
}

オートロード

ここから本題になります。オートロードに関しては、galluさんのnoteが非常に参考になりました。
それでは、以下の一行の詳細をみていきたいと思います。

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

まず、読み込んでいるファイルをみにいくと、以下のようになっています。
また、違うファイルを読み込んだ後に、クラスメソッドのgetLoader()を実行しています。

vendor/autoload.php
<?php

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

return ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a::getLoader();

getLoader()は、以下のようになっています。長いのでいくつかに分けてみていきます。

vendor/composer/autoload_real.php
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a
{
    private static $loader;

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', 'loadClassLoader'), true, true);

        self::$loader = $loader = new \Composer\Autoload\ClassLoader();

        spl_autoload_unregister(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', '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\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::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\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequirebee6542d79f53acb601ba3c7d134558a($fileIdentifier, $file);
        }

        return $loader;
    }
}

classLoaderのインスタンス化

まず、self::loaderprivate static $loader;と宣言しただけなので、nullとなります。そのため、はじめのif文はスルーします。
その後、spl_autoload_register()でオートロードの設定をしています。

spl_autoload_register()は、配列を与えると、クラスメソッドを登録できます。
ここでは、loadClassLoader()をオートロードしています。すなわち、今後使おうとしたクラスが宣言されてなかったら、loadClassLoader()が実行されます。

オートロードの設定後、新しいインスタンスを生成して、オートロードの設定を解除しています。

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

spl_autoload_register(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', 'loadClassLoader'), true, true);

self::$loader = $loader = new \Composer\Autoload\ClassLoader();

spl_autoload_unregister(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', 'loadClassLoader'));

loadClassLoaderをみてみると、以下のようになっています。
引数には、クラスの読み込みでエラーがあったときのクラス名が入っています。
読み込めなかったのがComposer\Autoload\ClassLoaderだったら、ファイルを読み込んでください、となります。

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

ClassLoaderをみてみると、クラスが宣言されています。ここではrequireしているだけなので、今すぐ何かが実行されるわけではないので、クラスを読み込んでるんだな、というくらいの理解に留めます。

vendor/composer/ClassLoader.php
class ClassLoader
{
    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    ...

$loaderの初期化

それでは、autoload_real.phpの続きをみていきます。

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

まずは1行目をみていきます。
PHP_VERSION_IDは、PHPのバージョンが定数になっており、50600はPHP5.6を意味します。今回はPHP7.3のためtrueとなります。
definedは引数の定数が宣言済みかどうかを検証します。
HHVM_VERSIONは実行環境がHHVMの場合に定義される定数で、通常はHHVMを使用していないので、定義されていません。
zend_loader_file_encoded()Zend Guardを使用している場合に、定義される関数のようです。

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

まとめると、以下のようになっているので、$useStaticLoadertrueが入ります。

true && true && (true || false)

となると、実行されるのは以下の部分だけとなります。ファイルを読み込んで、クラスメソッドを実行しています。
call_user_func()は引数で指定したコールバックを実行します。
今回の場合、普通に実行する場合と何が違うんだと思って、call_user_func()を使わないで実行してみると、エラーになりました。このあたりは理解できていません。。。

vendor/composer/autoload_real.php
if ($useStaticLoader) {
    require_once __DIR__ . '/autoload_static.php';

    call_user_func(\Composer\Autoload\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::getInitializer($loader));
}

読み込んでいるファイルは以下の通りです。クラスの宣言になります。中身は非常に長いですが、クラスプロパティに配列を代入しています。

vendor/composer/autoload_static.php
namespace Composer\Autoload;

class ComposerStaticInitbee6542d79f53acb601ba3c7d134558a
{
    public static $files = array (
        'ec07570ca5a812141189b1fa81503674' =>
            __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert/Functions.php',

        ...

では、クラスメソッドを見ていきたいと思います。
\Closure:bind()の中の無名関数の中で、プロパティが代入されています。
右辺の値は長くて分かりにくいですが、上で示したような配列が代入されたクラスプロパティとなります。

/vendor/composer/autoload_static.php
public static function getInitializer(ClassLoader $loader)
{
    return \Closure::bind(function () use ($loader) {
        $loader->prefixLengthsPsr4 = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$prefixLengthsPsr4;
        $loader->prefixDirsPsr4 = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$prefixDirsPsr4;
        $loader->prefixesPsr0 = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$prefixesPsr0;
        $loader->classMap = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$classMap;

    }, null, ClassLoader::class);
}

問題となるのが左辺で、$loaderにはClassLoaderのインスタンスが入っています。

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

ClassLoaderのプロパティをみてみると、以下のように代入しているプロパティは全てprivateとなっています。なぜComposerStaticInitbee65...クラスの中で、ClassLoaderprivateプロパティに代入できるんだろうか、となると思うのですが、それを解決するのが\Clousure::bind()となります。

vendor/composer/ClassLoader.php
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();

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

private $classMap = array();

\Closure::bind()は簡単にまとめると、関数を好きなスコープ内で実行することができます。これに関しては、公式ドキュメントだけだと分かりにくいと思いますので、galluさんのnoteも参照すると非常にいいと思います。
今回の場合は、bindの引数の無名関数を、第三引数のClassLoader::classのスコープにあるとして、実行しています。そのためprivateなプロパティも代入できています。

オートロードの設定

1行だけですが、詳しくみていきます。

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

registerメソッドは以下のようになっています。
$thisClassLoaderのインスタンスが入っています。ここではClassLoaderloadClassメソッドを、オートロードの設定の対象としています。

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

loadClass()メソッドをみていきます。if文の条件式でfindFile()メソッドが呼ばれています。もしファイルが見つかれば、includeFile()を実行してファイルを読み込んでいます。これが先ほどの一時的なものとは異なり、通常のオートロードになります。
今後はクラスが使用されると、まずこのメソッドが実行されることになります。

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

        return true;
    }
}

findFile()

findFile()メソッドは以下の通りです。ファイルを探して、そのファイルを返しています。
引数の$classにはクラスの名前(名前空間+クラス名)が入ります。

vendor/composer/ClassLoader.php
public function findFile($class)
{
    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;
}

findFile 前半部分

まず、はじめのif文をみていきます。$this->classMapには、vendor/composer/autoload_static.phpで代入した値が入っています。もしあらかじめ登録しておいたクラスであれば、指定したファイルを返します。

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

$this->classMapの例をみていきます。いくつかあるのですが、馴染みのありそうなのものをピックアップしました。Illuminateが何故vendor/laravel/framework/src/Illuminateを指すのか今まで分かっていませんでしたが、ここでようやく分かりました。

vendor/composer/autoload_static.php
 public static $classMap = array (
    'Illuminate\\Http\\Request' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Http/Request.php',
    'Illuminate\\Support\\Facades\\Auth' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Facades/Auth.php',
    'Illuminate\\Support\\Str' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Str.php',     

findFile()の、次のif文をみていきます。
$this->classMapAuthoritativeは、基本的にはfalseとなります。
$this->missingClasses[$class]は、以前にもクラスが見つからなかった場合のみ、trueが代入されています。
ここは、$this->classMapに登録されていない場合の検索が無効であったり、既に見つかってないことが分かっている場合にfalseを返します。

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

念のため、classMapAuthoritativeをみてみると、まず初期値はfalseとなっています。
setClassMapAuthoritative()を使用してtrueに設定すると、上述のif文の条件がtrueになり、findFile()falseを返すようになります。すなわち、$this->classMapに登録されていないクラスは、検索が無効になります。
setClassMapAuthoritative()は、検索してみても見つからなかったので、$this->classMapAuthoritativeは基本的にはfalseと考えていいと思います。

vendor/composer/ClassLoader.php
private $classMapAuthoritative = false;

...

/**
 * Turns off searching the prefix and fallback directories for classes
 * that have not been registered with the class map.
 *
 * @param bool $classMapAuthoritative
 */
public function setClassMapAuthoritative($classMapAuthoritative)
{
    $this->classMapAuthoritative = $classMapAuthoritative;
}

findFile()の、次のif文をみていきます。$this->apcuPrefixは調べてみても理解できませんでした。キャッシュドライバにAPCを利用している場合に値がsetされている、と思っていますが定かではありません。今回は、nullが入っていたので、この文はスキップします。

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

findFileWithExtension()

findFile()の次の1行になります。インスタンスメソッドがでてきたので見ていきます。

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

引数の$classにはクラス名、$extには拡張子の.phpという文字列が入っています。非常に長いので、ここも少しずつみていきます。

vendor/composer/ClassLoader.php
private function findFileWithExtension($class, $ext)
{
    // PSR-4 lookup
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

    $first = $class[0];
    if (isset($this->prefixLengthsPsr4[$first])) {
        $subPath = $class;

        while (false !== $lastPos = strrpos($subPath, '\\')) {
            $subPath = substr($subPath, 0, $lastPos);
            $search = $subPath . '\\';
            if (isset($this->prefixDirsPsr4[$search])) {
                $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                foreach ($this->prefixDirsPsr4[$search] as $dir) {
                    if (file_exists($file = $dir . $pathEnd)) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-4 fallback dirs
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }

    // PSR-0 lookup
    if (false !== $pos = strrpos($class, '\\')) {
        // namespaced class name
        $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
            . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
    } else {
        // PEAR-like class name
        $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
    }

    if (isset($this->prefixesPsr0[$first])) {
        foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
            if (0 === strpos($class, $prefix)) {
                foreach ($dirs as $dir) {
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-0 fallback dirs
    foreach ($this->fallbackDirsPsr0 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
            return $file;
        }
    }

    // PSR-0 include paths.
    if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
        return $file;
    }

    return false;
}
PSR-4 lookup

はじめの1行をみていきます。
strtr()は、文字列を置換する組み込み関数になります。
クラス名は、各階層がバックスラッシュ\で区切られているので、それをDIRECTORY_SEPARATOR(自分の環境ではスラッシュ/)に置換しています。その後、拡張子と結合することでファイルのパスを作成しています。

vendor/composer/ClassLoader.php
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

次のif文ですが、まず$firstにはクラス名の1文字目を代入しています。
その文字が$this->prefixLengthsPsr4に代入されていた場合に処理が走ります。
ちなみに、var_dumpで確認したところ、Symfony\Component\Translation\TranslatorInterfaceが呼ばれているようですので、具体例として採用して見ていきたいと思います。

vendor/composer/ClassLoader.php
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
   ...
}

まず$this->prefixLengthsPsr4vendor/composer/autoload_static.phpで既に代入されており、以下のような配列になっています。
Symfony\Component\Translation\TranslatorInterfaceの1文字目はSなので、Sがキーとなる値はあるかどうか確認してみます。以下の通り、Sがキーとなる配列が存在しているので、上述のif文はtrueとなります。

vendor/composer/autoload_static.php
public static $prefixLengthsPsr4 = array (
        'v' => 
        array (
            'voku\\' => 5,
        ),

        ...

        'S' => 
        array (
            'Symfony\\Polyfill\\Php80\\' => 23,
            'Symfony\\Polyfill\\Php73\\' => 23,
            'Symfony\\Polyfill\\Php70\\' => 23,

            ...

        ),

続きをみていきます。まず$subPathに値を代入しています。
strrposは、文字列中に、指定した文字が最後に現れる場所を返します。
今回の場合、Symfony\Component\Translation\TranslatorInterfaceで、\が最後に現れるのは、29文字目となるので、29を返します。
したがって条件式はfalse !== 29となり、while文は実行されます。

vendor/composer/ClassLoader.php
$subPath = $class;

while (false !== $lastPos = strrpos($subPath, '\\')) {
   ...
}

while文の中身をみていきます。
substrは、第一引数の文字列の、第二引数から第三引数までの部分を返します。今回はSymfony\Component\Translation\TranslatorInterfaceからSymfony\Component\Translationを取り出します。
$searchでは末尾に\を追加して、$this->prefixDirsPsr4にあるかどうか確認しています。

vendor/composer/ClassLoader.php
while (false !== $lastPos = strrpos($subPath, '\\')) {
    $subPath = substr($subPath, 0, $lastPos);
    $search = $subPath . '\\';
    if (isset($this->prefixDirsPsr4[$search])) {
        $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
        foreach ($this->prefixDirsPsr4[$search] as $dir) {
            if (file_exists($file = $dir . $pathEnd)) {
                return $file;
            }
        }
    }
}

ディレクトリは登録されているので、trueを返すことが分かります。

vendor/composer/autoload_static.php
public static $prefixDirsPsr4 = array (

     ...

    'Symfony\\Component\\Translation\\' => 
    array (
        0 => __DIR__ . '/..' . '/symfony/translation',
    ),

    ...

);

if文の中身をみていきます。
$logicalPathPsr4の中身はSymfony/Component/Translation/TranslatorInterface.phpとなっており、
substr($logicalPathPsr4, $lastPos + 1)$lastPos + 1の場所から後ろ全部の文字列を取り出しますので、TranslatorInterface.phpを返します。
したがって、$pathEndには/TranslatorInterface.phpが入ります。
$this->prefixDirsPsr4[$search]は複数のディレクトリが入っている場合もあるので、foreachで各ディレクトリごとにファイルがあるかどうかを確認しています。
ファイルが見つかれば、そのファイルを返します。
ちなみに、vendor/composer/../symfony/translation/TranslatorInterface.phpは見つからないので、次に進みます。

vendor/composer/ClassLoader.php
if (isset($this->prefixDirsPsr4[$search])) {
    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
    foreach ($this->prefixDirsPsr4[$search] as $dir) {
        if (file_exists($file = $dir . $pathEnd)) {
            return $file;
        }
    }
}
PSR-4 fallback dirs

findFileWithExtension()の次の文になりますが、$this->fallbackDirsPsr4は設定されていないので、処理は行われません。

vendor/composer/ClassLoader.php
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
        return $file;
    }
}
PSR-0 lookup

$posは先ほどみたとおり29となるので、条件式はtrueとなります。
ここでのstrtr()は文字列中に_が含まれる場合は/に変換しています。substr($logicalPathPsr4, $pos + 1)にはTranslatorInterface.phpが入っているので、特に変換することなく文字列を結合しています。
結果、$logcalPathPsr0にはSymfony/Component/Translation/TranslatorInterface.phpが代入されます。

vendor/composer/ClassLoader.php
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
    // namespaced class name
    $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
        . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
    ...
}

その後、$this->prefixesPsr0[$first]に登録されているかどうか確認しますが、登録されていないので処理は行われません。

vendor/composer/ClassLoader.php
if (isset($this->prefixesPsr0[$first])) {
    foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
        if (0 === strpos($class, $prefix)) {
            foreach ($dirs as $dir) {
                if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                    return $file;
                }
            }
        }
    }
}

ちなみに、中身は以下の通りです。

vendor/composer/autoload_static.php
public static $prefixesPsr0 = array (
    'M' => 
    array (
        'Mockery' => 
        array (
            0 => __DIR__ . '/..' . '/mockery/mockery/library',
        ),
    ),
    'H' => 
    array (
        'Highlight\\' => 
        array (
            0 => __DIR__ . '/..' . '/scrivo/highlight.php',
        ),
        'HighlightUtilities\\' => 
        array (
            0 => __DIR__ . '/..' . '/scrivo/highlight.php',
        ),
    ),
);
PSR-0 fallback dirs

$this->fallbackDirsPsr0は登録されていないので、処理は行われません。

vendor/composer/ClassLoader.php
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
        return $file;
    }
}
PSR-0 include paths

$this->useIncludePathの初期値はfalseとなります。特に設定しなければfalseのため、ここも処理は行われません。どうやら、Symfony\Component\Translation\TranslatorInterfaceのファイルは見つからないので、falseを返すようです。

vendor/composer/ClassLoader.php
    // PSR-0 include paths.
    if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
        return $file;
    }

    return false;
}

findFile 後半部分

ようやくfindFile()に戻ってこれました。
$fileには$loader->classMapに登録されているものはファイルのパスが返され、Symfony\Component\Translation\TranslatorInterfacefalseが返されています。
はじめのif文ですが、$fileにファイルのパスが返ってきていても、右辺がfalseとなるので、条件式はfalseとなります。
次の$this->apcuPrefixnullとなります。
次のif文で、Symfony\Component\Translation\TranslatorInterfaceはみつからなかったので、missingClassesに代入して、次からは検索する前にfalseを返すようにしています。
最後に、$fileを返しています。

vendor/composer/ClassLoader.php
public function findFile($class)
{
    ...

    $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;
}

includeFile

ようやくloadClass()メソッドに戻ってきました。$this->findFile($class)はファイルのパス or falseが格納されています。つまり、ファイルのパスが見つかった場合はincludeFile()を実行し、見つからなかった場合はなにもしません。
オートロードでこのような関数になっているため、クラスが使用されると、このloadClassa()が実行され、ファイルのパスが見つかれば読み込んで、見つからない場合はエラーとなります。

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

        return true;
    }
}

includeFile()は以下の通りです。ファイルを読み込んでいるだけです。

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

組み込み関数の読み込み

長かった$loader->register(true);も終わり、autoload_real.phpに戻ってきました。
$useStaticLoadertrueなので、$includeFilesに値が代入されます。
その値をforeachでループして、composerRequire...()という関数を実行しています。
最後に$loaderを返して終わりです。

vendor/composer/autoload_real.php
    if ($useStaticLoader) {
        $includeFiles = Composer\Autoload\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$files;
    } else {
        $includeFiles = require __DIR__ . '/autoload_files.php';
    }

    foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequirebee6542d79f53acb601ba3c7d134558a($fileIdentifier, $file);
    }

    return $loader;
}

$filesプロパティは以下の通りです。数字の羅列のキーに対して、ファイルのパスが値となっています。

vendor/composer/autoload_static.php
class ComposerStaticInitbee6542d79f53acb601ba3c7d134558a
{
    public static $files = array (
        'ec07570ca5a812141189b1fa81503674' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
        'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',

        ...

composerRequire...()という関数は以下のようになっています。
$GLOBALS['__composer_autoload_files']は、上述の数字の羅列をキーとする配列となっており、まだファイルを読み込んでいない場合はファイルを読み込んでtrueを代入しています。

vendor/composer/autoload_real.php
function composerRequirebee6542d79f53acb601ba3c7d134558a($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

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

例として、以下のファイルをみてみたいと思います。

vendor/composer/autoload_static.php
'f0906e6318348a765ffb6eb24e0d0938' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/helpers.php',

ファイルをみてみると、多くの関数が定義されています。他のファイルも同様の構造となっており、Laravelで使える関数が定義されています。
なかでも利用頻度の高そうなview()をみてみたいと思います。ここで定義しているから、コントローラからreturn view();とできることが分かります。
処理の内容はサービスコンテナの理解が必要で、とても難しいので、また別の機会に。。。

vendor/laravel/framewordk/src/Illuminate/Foundation/helpers.php
<?php

use Illuminate\Contracts\View\Factory as ViewFactory;

...

if (! function_exists('view')) {
    /**
     * Get the evaluated view contents for the given view.
     *
     * @param  string|null  $view
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $data
     * @param  array  $mergeData
     * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
     */
    function view($view = null, $data = [], $mergeData = [])
    {
        $factory = app(ViewFactory::class);

        if (func_num_args() === 0) {
            return $factory;
        }

        return $factory->make($view, $data, $mergeData);
    }
}

return $loader;の後

getLoader()$loaderを返すところまでみてきたので、autoload.phpに戻ってきました。
ここでは、$loaderをそのままreturnしています。

vendor/autoload.php
<?php

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

return ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a::getLoader();

$loaderを返された先のindex.phpですが、特に$loaderの値を取得することはなく、autoload.phpのファイル読み込みで終わっています。非常に長かったですが、今回は
これで終わりです。

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

まとめ

require __DIR__.'/../vendor/autoload.php';の1行で、クラスのオートロード設定、組み込み関数の宣言などを実行していることが分かりました。
また、フレームワークの中身を調べていくことで、自分が知らないPHPの組み込み関数や、いいコードの書き方など、非常に勉強になりました。
次の1行の$app = require_once __DIR__.'/../bootstrap/app.php';も非常に難しいので、読み解いていきたいと思います。

参考サイト

ksrnnb
ReactとLaravelでアプリを制作中。 初心者。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away