#初めに
皆さん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
のファイル構造は以下のようになっています。//
以降はファイルごとの役割を表しています。
▾ 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 @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
//autoload_real.phpのgetLoader()へautoloadの処理を実行するように命令し、結果を返す
return ComposerAutoloaderInit35d447dc237e2326be89407c57b5ca8b::getLoader();
ちなみにLaravelでautoload.php
はこんなところに書かれています。
//ここへ読み込むことで、Laravelの処理のすべてでautloadが使用できるようになる
require __DIR__.'/../vendor/autoload.php';
###autoload
の実行
autoload.php
で処理を命令されたgetLoader()
は大きく分けて3つのことを行います。
- 読み込むファイルのデータが格納されたファイル(autoload_static.phpなど)に、
autoload
の実態があるファイル(ClassLoader.php)へデータを渡すよう命令。 -
autoload
を実行させる - その他必要なファイル(これらがどう必要かあまりわかっていません)を読み込み
実際のコードはこちら
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
のコードを見てみましょう。
//名前空間+クラス名をキー、絶対パスを値とした連想配列でデータを格納
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.php
はautoloa
の実態ということもあって、やることは送られてきたデータをただ読み込むだけです。コードはこちら
//ファイルを読み込む関数の呼び出し
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メソッドの機能とその仕組みについて