クラスを分割してファイルを整理すると、こんどは読み込みが面倒になります。エントリーポイントの先頭に require_once が何十行も並び、ファイルを追加するたびに書き足す。順番を間違えると「クラスが見つからない」というエラーが出る。よくある状況です。
spl_autoload_register() を使えば、この作業から解放されます。クラスが実際に使われた瞬間に、そのファイルだけを読み込む仕組みを登録できるからです。
この記事では、オートロードの基本的な仕組みから、名前空間とディレクトリを対応づける実装、複数のオートローダーを登録したときの動き、そして Composer との関係までを解説します。
目次
オートロードは「見つからないときに呼ばれる処理」
PHP は、まだ定義されていないクラスが使われたとき、すぐにエラーを出すわけではありません。その前に、登録されているオートローダーを呼び出します。オートローダーの中でファイルを読み込んでクラスが定義されれば、処理はそのまま続きます。
登録には spl_autoload_register() を使います。引数には、クラス名を受け取る関数を渡します。
<?php
spl_autoload_register( function ( $class ) {
$file = __DIR__ . '/classes/' . $class . '.php';
if ( file_exists( $file ) ) {
require_once $file;
}
} );
// この時点では classes/User.php はまだ読み込まれていない
$user = new User(); // ← ここでオートローダーが呼ばれる
オートローダーに渡ってくるのは、名前空間まで含んだ完全なクラス名です。先頭のバックスラッシュは付きません。App\Models\User を使ったなら、文字列 "App\Models\User" が渡ってきます。
対象になるのはクラスだけではありません。インターフェース、トレイト、列挙型(enum)も、未定義のまま使われればオートローダーが呼ばれます。
名前空間とディレクトリを対応づける
実用的なオートローダーを書くには、名前空間の区切り(\)をディレクトリの区切り(/)に変換します。これが PSR-4 と呼ばれる広く使われている規約の考え方です。
たとえば、次のようなファイル構成を考えます。
my-plugin/
├── bootstrap.php
└── src/
├── Models/
│ └── User.php → MyPlugin\Models\User
└── Services/
└── Mailer.php → MyPlugin\Services\Mailer
<?php
namespace MyPlugin\Models;
class User {
public function __construct( private string $name ) {}
public function get_name(): string {
return $this->name;
}
}
これを読み込むオートローダーは次のように書けます。自分の名前空間で始まるクラスだけを担当し、それ以外は何もせずに戻るのがポイントです。
<?php
spl_autoload_register( function ( $class ) {
// 担当する名前空間の接頭辞と、対応するディレクトリ
$prefix = 'MyPlugin\\';
$base_dir = __DIR__ . '/src/';
// 担当外のクラスなら何もしない(他のオートローダーに任せる)
if ( 0 !== strncmp( $prefix, $class, strlen( $prefix ) ) ) {
return;
}
// 接頭辞を取り除いた残りが、ディレクトリ以下のパスになる
$relative = substr( $class, strlen( $prefix ) );
// MyPlugin\Models\User → Models/User.php
$file = $base_dir . str_replace( '\\', '/', $relative ) . '.php';
if ( file_exists( $file ) ) {
require_once $file;
}
} );
<?php
require_once __DIR__ . '/bootstrap.php';
use MyPlugin\Models\User;
// require はこれ1行だけ。User.php は自動で読み込まれる
$user = new User( '山田太郎' );
echo $user->get_name();
クラスを増やしても bootstrap.php を触る必要はありません。命名のルールどおりにファイルを置くだけで読み込まれます。
複数のオートローダーを登録する
spl_autoload_register() は何回でも呼べます。登録されたオートローダーは登録した順に呼ばれ、クラスが定義された時点で打ち切られます。だから、自分のオートローダーが担当外のクラスに対して何もせず return することが大切です。
| 引数 | 意味 |
|---|---|
| 第1引数 | オートローダーとして登録する呼び出し可能な値 |
第2引数 $throw | 登録に失敗したときに例外を投げるか(既定は true) |
第3引数 $prepend | true にすると、既存のオートローダーより先に呼ばれる |
クラスを使った関数やメソッドを渡すこともできます。設定をまとめたクラスとして書くなら、次のような形になります。
<?php
class Autoloader {
public function __construct(
private string $prefix,
private string $base_dir
) {}
public function register(): void {
spl_autoload_register( array( $this, 'load' ) );
}
public function load( string $class ): void {
if ( 0 !== strncmp( $this->prefix, $class, strlen( $this->prefix ) ) ) {
return;
}
$relative = substr( $class, strlen( $this->prefix ) );
$file = $this->base_dir . str_replace( '\\', '/', $relative ) . '.php';
if ( is_readable( $file ) ) {
require_once $file;
}
}
}
// 名前空間ごとに登録できる
( new Autoloader( 'MyPlugin\\', __DIR__ . '/src/' ) )->register();
( new Autoloader( 'MyVendor\\', __DIR__ . '/lib/' ) )->register();
登録済みのオートローダーは spl_autoload_functions() で一覧を取得でき、spl_autoload_unregister() で解除できます。他のライブラリと競合したときの調査に使えます。
Composer を使う場合
実務では、自分でオートローダーを書くより Composer に任せることのほうが多いはずです。Composer が生成する vendor/autoload.php も、内部では spl_autoload_register() を使っています。
{
"autoload": {
"psr-4": {
"MyPlugin\\": "src/"
}
}
}
# autoload の設定を変えたら実行する
$ composer dump-autoload
# 本番向けにクラスとファイルの対応表を作っておく(高速化)
$ composer dump-autoload --optimize
あとは require __DIR__ . '/vendor/autoload.php'; の1行を書けば、自分のクラスも外部ライブラリも読み込まれます。
では spl_autoload_register() を自分で書く意味はどこにあるのか。Composer を使えない、あるいは使いたくない場面があるからです。WordPress のプラグインのように配布先の環境が分からない場合や、vendor ディレクトリを同梱したくない小規模なプラグインでは、数行のオートローダーを自前で持つほうが取り回しがよくなります。
クラスが読み込まれないときの確認
ファイル名とクラス名が一致していない
最も多い原因です。Linux のサーバーでは大文字と小文字が区別されるため、User.php と user.php は別物です。macOS や Windows では区別されないことがあり、手元では動くのに本番で「クラスが見つからない」と言われる典型的なパターンになります。
クラス名とファイル名を完全に一致させる、というルールを徹底してください。名前空間の階層とディレクトリの階層も同じにそろえます。
名前空間の宣言を忘れている
ファイルは正しい場所にあるのに読み込まれない場合、ファイルの先頭の namespace 宣言を確認してください。src/Models/User.php に置いているのに namespace MyPlugin; と書いていると、実際のクラス名は MyPlugin\User になり、オートローダーが探すパスと食い違います。
エラーメッセージが出ずに止まる
オートローダーの中で file_exists() を確認せずに require すると、ファイルが無い場合に致命的エラーになります。逆に、確認だけして何もしないと、Class "MyPlugin\Models\User" not found という分かりやすいエラーになります。原因を追いやすいのは後者なので、存在確認は必ず入れておきましょう。
デバッグのときは、オートローダーの中で受け取ったクラス名と組み立てたパスを出力してみると、どこで食い違っているかがすぐ分かります。
<?php
spl_autoload_register( function ( $class ) {
error_log( "autoload: {$class}" );
// …パスを組み立てる処理…
error_log( " → {$file} : " . ( file_exists( $file ) ? 'found' : 'NOT FOUND' ) );
} );
__autoload が使えない
古い記事では __autoload() というグローバル関数が紹介されていることがあります。これは PHP 7.2 で非推奨となり、PHP 8.0 で削除されました。1つしか登録できないという制約もあったため、現在は spl_autoload_register() だけを使います。
まとめ
spl_autoload_register() は、未定義のクラスが使われたときに呼ばれる処理を登録する関数です。そこでクラス名からファイルパスを組み立てて読み込めば、require を並べる必要がなくなります。
実装のポイントは3つです。担当する名前空間で始まらないクラスは何もせずに戻ること、名前空間の区切りをディレクトリの区切りに置き換えること、そして読み込む前にファイルの存在を確認することです。
Composer が使える環境なら composer.json に PSR-4 の設定を書くだけで済みます。自前で書くのは、Composer を前提にできない配布物を作るときだと考えておけばよいでしょう。どちらを使うにせよ、クラス名とファイル名をそろえるという基本は変わりません。