LoginSignup
8
2

More than 1 year has passed since last update.

コードから読み解くautoloadの仕組み ~第一層 大雑把にautoloaderの仕組みを理解しよう~

Last updated at Posted at 2020-05-15

#初めに
 皆さんcomposerから利用できるautoloadはご存じでしょうか?PHPのFWであるLaravelなどを使用される方の多くは使用されたことがあるかと思います。自分がautoloadと出会ったのはLaravelを触れたあたりで、初めて見たときは、
「いちいちrequireしなくてもほかのファイルの機能が使えるのは便利だな~。」
と思っていました。そして使っていくうちに、
「自分が勝手に追加したファイルもちゃんと読み込んでくれている!怖っ!!(謎の恐怖)」
と思うようになり、だんだんその恐怖心が強まってきたのである日気が済むまでautoloadの仕組みを調べました。そして調べた内容を自分のうちにとどめておいても仕方ないし、共有することで自分の考えがよりまとまったり、指摘による考えの修正が起こるかもしれないと思ったのでここへ書いてみました。
 もしautoloadの仕組みに興味があったり、自分と同じような恐怖感に覚えがあれば見てみてください。

#この記事の流れ
autoloadの仕組みはこの記事を含めて3回に分けて紹介していこうと思っています。
ちなみにこの表の深さの欄が下に行けば行くほど扱う内容がニッチで複雑なものになっていきます。もしautoloadの仕組みを軽く知りたいのであれば、一層だけでも十分だと思います。

深さ タイトル 内容
第一層 大雑把にautoloaderの仕組みを理解しよう autoloadで使用するファイルの働きからautoloadの仕組みを理解する
第二層 より深くautoloadの仕組みを理解しよう 第一層で扱ったファイルのコードからautoloadの仕組みを理解する

※第二層、三層は後日公開予定(2020/05/15時点)
第二層の記事を公開しました!

#扱うツールついて
第一~三層で扱うツールは以下の通りです。

ツール バージョン
Laravel 7.11.0
Composer 1.10.5

また、今回解説するComposerはLaravelインストール時に自動でインストールされたものであるので、Composerからautoloadを単体でインストールした場合と異なる点があるかもしれません。

#第一層 大雑把にautoloadの仕組みを理解しよう
※タイトル詐欺のようで申し訳ないのですが、この記事ではファイル内のコードの説明をあまりしません。コードの説明は第二層で説明するので、第二層が公開されるまでしばしお待ちください。

##aoutloadの構造
autoloadのファイル構造は以下のようになっています。//以降はファイルごとの役割を表しています。

autoload
▾ vendor/                  ※読み込むファイルのデータを格納
  ▾ composer/
      autoload_classmap.php //※
      autoload_files.php //※
      autoload_namespaces.php //※
      autoload_psr4.php //※
      autoload_real.php //autoloadの実行
      autoload_static.php //※
      ClassLoader.php //autoloadの実態
      installed.json
      LICENSE
  autoload.php //autoloadの入り口

##ファイルごとの役割
次は役割ごとにファイルを分けて、ファイルの中で何が行われているか見てみます。あと実際のコードも載せておきます。

###autoloadの入り口
autoloadの入り口はautoload.phpが担います。詳しく説明すると、phpの実行が行われる同時にautoloadの実行を行うautoload_real.phpのある関数へ処理を行うよう命令し、その結果を返します。コードを見るとこんな感じ

autoload.php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
//autoload_real.phpのgetLoader()へautoloadの処理を実行するように命令し、結果を返す
return ComposerAutoloaderInit35d447dc237e2326be89407c57b5ca8b::getLoader(); 

ちなみにLaravelでautoload.phpはこんなところに書かれています。

/public/index.php
//ここへ読み込むことで、Laravelの処理のすべてでautloadが使用できるようになる
require __DIR__.'/../vendor/autoload.php'; 

###autoloadの実行
autoload.phpで処理を命令されたgetLoader()は大きく分けて3つのことを行います。

  1. 読み込むファイルのデータが格納されたファイル(autoload_static.phpなど)に、autoloadの実態があるファイル(ClassLoader.php)へデータを渡すよう命令。
  2. autoloadを実行させる
  3. その他必要なファイル(これらがどう必要かあまりわかっていません)を読み込み
    実際のコードはこちら
autoload_real.php
public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInit35d447dc237e2326be89407c57b5ca8b', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit35d447dc237e2326be89407c57b5ca8b', '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';

//1. 読み込むファイルのデータが格納されたファイル(autoload_static.phpなど)に、`autoload`の実態があるファイル(ClassLoader.php)へデータを渡すよう命令 
          call_user_func(\Composer\Autoload\ComposerStaticInit35d447dc237e2326be89407c57b5ca8b::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);
            }
        }
//2. `autoload`を実行させる
        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit35d447dc237e2326be89407c57b5ca8b::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
//3. その他必要なファイル(これらがどう必要かあまりわかっていません)を読み込み
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire35d447dc237e2326be89407c57b5ca8b($fileIdentifier, $file);
        }

        return $loader;
    }
}

###読み込むファイルのデータを格納
この役割を担うファイルは複数ありますが、開発環境によって使用するファイルが異なるので一度にすべてのファイルが実行されるわけではありません。また、ファイルごとに格納されたデータを紹介すると、

  • autoload_classmap.php ⇒ Laravelで使用するファイルの名前空間+クラス名をキー、そのファイルの絶対パスを値とした連想配列が格納されている。
  • autoload_files.php ⇒ Laravelを使用するうえで重要であろうファイル(自分はよくわかっていない)の配列が格納されている
  • autoload_namespaces.php ⇒ Laravelで使用するファイルの名前空間をキー、そのファイルの絶対パスを値とした連想配列が格納されている。
  • autoload_psr4.php ⇒ Laravelで使用するファイルの名前空間をキー、そのファイルの絶対パスを値とした連想配列が格納されている。
  • autoload_static.php ⇒ autoload_classmap.php +autoload_files.php+ autoload_namespaces.php + autoload_psr4.phpのデータが格納されている。現在のPHPのバージョンの関係上、autoloadではこれらの4つのファイルの代わりにこのファイルが実行される。

読み込むファイルのデータのされ方と、どのようにしてそのデータをautoloadの実態であるClassLoader.phpへ送っているのかを確認するために、autoload_static.phpのコードを見てみましょう。

autoload_static.php
//名前空間+クラス名をキー、絶対パスを値とした連想配列でデータを格納
public static $classMap = array (
        'App\\Console\\Kernel' => __DIR__ . '/../..' . '/app/Console/Kernel.php',
        'App\\Eloquent\\Contribute' => __DIR__ . '/../..' . '/app/Eloquent/Contribute.php',
        'App\\Exceptions\\Handler' => __DIR__ . '/../..' . '/app/Exceptions/Handler.php',
        'App\\Http\\Controllers\\Contribute' => __DIR__ . '/../..' .  '/app/Http/Controllers/Controller.php',
        'App\\Http\\Kernel' => __DIR__ . '/../..' . '/app/Http/Kernel.php',
        'App\\Http\\Middleware\\Authenticate' => __DIR__ . '/../..' . '/app/Http/Middleware/Authenticate.php',
        'App\\Http\\Middleware\\CheckForMaintenanceMode' => __DIR__ . '/../..' . '/app/Http/Middleware/CheckForMaintenanceMode.php',
        'App\\Http\\Middleware\\EncryptCookies' => __DIR__ . '/../..' . '/app/Http/Middleware/EncryptCookies.php',
        'App\\Http\\Middleware\\RedirectIfAuthenticated' => __DIR__ . '/../..' . '/app/Http/Middleware/RedirectIfAuthenticated.php',
        'App\\Http\\Middleware\\TrimStrings' => __DIR__ . '/../..' . '/app/Http/Middleware/TrimStrings.php',
        'App\\Http\\Middleware\\TrustProxies' => __DIR__ . '/../..' . '/app/Http/Middleware/TrustProxies.php',
        'App\\Http\\Middleware\\VerifyCsrfToken' => __DIR__ . '/../..' . '/app/Http/Middleware/VerifyCsrfToken.php',
        'App\\Model\\Contribute\\Input' => __DIR__ . '/../..' . '/app/Model/Contribute/Input.php',
        'App\\Providers\\AppServiceProvider' => __DIR__ . '/../..' . '/app/Providers/AppServiceProvider.php',
        'App\\Providers\\AuthServiceProvider' => __DIR__ . '/../..' . '/app/Providers/AuthServiceProvider.php',
        'App\\Providers\\BroadcastServiceProvider' => __DIR__ . '/../..' . '/app/Providers/BroadcastServiceProvider.php',
        'App\\Providers\\EventServiceProvider' => __DIR__ . '/../..' . '/app/Providers/EventServiceProvider.php',
        'App\\Providers\\RouteServiceProvider' => __DIR__ . '/../..' . '/app/Providers/RouteServiceProvider.php',
        'App\\TestModel' => __DIR__ . '/../..' . '/app/TestModel.php',
        'App\\User' => __DIR__ . '/../..' . '/app/User.php',
//省略

//ClassLoader.phpへデータを送信
public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit35d447dc237e2326be89407c57b5ca8b::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit35d447dc237e2326be89407c57b5ca8b::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInit35d447dc237e2326be89407c57b5ca8b::$prefixesPsr0;
            $loader->classMap = ComposerStaticInit35d447dc237e2326be89407c57b5ca8b::$classMap;

        }, null, ClassLoader::class);
    }

###autoloadの実態
ClassLoader.phpautoloaの実態ということもあって、やることは送られてきたデータをただ読み込むだけです。コードはこちら

ClassLoader.php
//ファイルを読み込む関数の呼び出し
public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

//データの読み込みとデータに基づくファイルの読み込み
public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file); //ここはただ include $file してるだけ。

            return true;
        }
    }

//送られてきたデータを取得
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;
    }

#終わりに
 今回の記事は以上となります。autoloadのファイルに書かれたコードはPHPのマジックメソッドを使って作られているので、解読は自力でもやりやすかったです。(それでも構造の理解は結構大変だった...)
この関数はどう使われているのか疑問に思った方は、後日公開される第二層の記事を楽しみにしてください。こちらほうは今回あっさりとしか紹介しなかった関数などについてじっくり紹介していきます。

あとLaravelに関しての記事もいくつか書いているので、こちらも読んでいただけるとありがたいです!!
Laravelのサービスコンテナのバインドと解決の仕組みが知りたい!
Laravelのsingletonメソッドの機能とその仕組みについて

8
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2