[PHP]spl_autoload_extensions完全解説|オートロード対象の拡張子を設定・管理する方法

PHP

はじめに

PHPのデフォルトオートローダー spl_autoload() はクラス名からファイルパスを自動生成しますが、その際にどの拡張子のファイルを検索するかを制御しているのが spl_autoload_extensions() です。

デフォルトでは .inc,.php の2種類が対象ですが、この設定を変更することで .php のみに絞ったり、プロジェクト固有の拡張子を追加したりできます。

関数としてはシンプルですが、spl_autoload()spl_autoload_register() と密接に連携しているため、オートロード全体の挙動に影響を与える重要な設定ポイントです。


関数の基本情報

項目内容
関数名spl_autoload_extensions()
利用可能バージョンPHP 5.1以降
所属SPL(Standard PHP Library)
戻り値string(現在の拡張子設定)
拡張機能SPL(デフォルトで有効)

シグネチャ

spl_autoload_extensions(?string $file_extensions = null): string

パラメータ

パラメータ説明
$file_extensionsstring|null設定する拡張子(カンマ区切り)。nullまたは省略で現在値を返すだけ

戻り値

変更前後を問わず、現在設定されている拡張子文字列を返します。

// 引数なし → 現在値を取得
$current = spl_autoload_extensions();       // ".inc,.php"

// 引数あり → 設定して現在値(設定後の値)を返す
$new = spl_autoload_extensions('.php');     // ".php"

デフォルト値と設定の影響範囲

項目内容
デフォルト値.inc,.php
影響する関数spl_autoload() のみ
影響しない関数spl_autoload_register() で登録したカスタムハンドラ
スコーププロセス全体(グローバル設定)

重要: spl_autoload_extensions() の設定は spl_autoload() というデフォルトハンドラのみに影響します。spl_autoload_register() で登録したカスタムクロージャやクラスメソッドは、この設定を参照しません。独自ハンドラを使う場合は拡張子の扱いを自分で実装します。


spl_autoload() のファイル検索フロー

spl_autoload('App\Model\User') が呼ばれる

     ↓ クラス名を変換
     └─ 小文字化 + \ → / 変換
        → "app/model/user"

     ↓ include_path の各ディレクトリを走査
        → "/var/www/src", ".", "/usr/share/php"

     ↓ spl_autoload_extensions() の各拡張子を試行
        → "app/model/user.inc"   (.inc を先に試す)
        → "app/model/user.php"   (.php を次に試す)

     ↓ 最初にヒットしたファイルを require

実践サンプル集(PHP 8.x対応)

サンプル1:基本的な取得と設定

<?php
declare(strict_types=1);

// デフォルト値を確認
$default = spl_autoload_extensions();
echo "デフォルト: {$default}\n"; // .inc,.php

// .php のみに絞る(レガシーな .inc を除外)
spl_autoload_extensions('.php');
echo "変更後: " . spl_autoload_extensions() . "\n"; // .php

// 複数の拡張子を設定
spl_autoload_extensions('.php,.class.php,.inc.php');
echo "複数設定: " . spl_autoload_extensions() . "\n"; // .php,.class.php,.inc.php

// 元に戻す
spl_autoload_extensions($default);
echo "復元: " . spl_autoload_extensions() . "\n"; // .inc,.php

実行結果:

デフォルト: .inc,.php
変更後: .php
複数設定: .php,.class.php,.inc.php
復元: .inc,.php

解説: 設定はプロセス全体に影響するグローバル値です。変更前に現在値を退避しておくと、後で確実に元に戻せます。


サンプル2:推奨構成 — .php のみに絞る

現代のPHPプロジェクトでの標準的な設定です。

<?php
declare(strict_types=1);

// .inc はレガシーな拡張子。現代のプロジェクトでは .php のみを推奨
spl_autoload_extensions('.php');

// include_path にソースディレクトリを追加
set_include_path(
    get_include_path()
    . PATH_SEPARATOR . __DIR__ . '/src'
    . PATH_SEPARATOR . __DIR__ . '/lib'
);

// spl_autoload 自体をハンドラとして登録
spl_autoload_register('spl_autoload');

// 検索順序の確認
echo "拡張子設定: " . spl_autoload_extensions() . "\n";
echo "include_path:\n";
foreach (explode(PATH_SEPARATOR, get_include_path()) as $path) {
    echo "  {$path}\n";
}

/*
 * 以降のクラス参照で自動ロードされるファイルパスの例:
 *
 * クラス名: App\Model\User
 * → src/app/model/user.php
 * → lib/app/model/user.php
 *
 * クラス名: Util\StringHelper
 * → src/util/stringhelper.php
 * → lib/util/stringhelper.php
 */

実行結果:

拡張子設定: .php
include_path:
  .
  /var/www/project/src
  /var/www/project/lib

解説: .inc は古いPHPプロジェクトで使われた拡張子です。ウェブサーバーで直接アクセスされると内部コードが露出するリスクがあるため、現代のプロジェクトでは .php のみを使うことが推奨されます。


サンプル3:設定を安全に管理するラッパークラス

グローバル設定を安全に変更・復元する管理クラスです。

<?php
declare(strict_types=1);

/**
 * spl_autoload_extensions のグローバル設定を安全に管理するクラス
 */
class AutoloadExtensionManager
{
    private string $current;
    private string $original;

    public function __construct()
    {
        $this->original = spl_autoload_extensions();
        $this->current  = $this->original;
    }

    /**
     * 拡張子一覧を配列で取得
     *
     * @return list<string>
     */
    public function getExtensions(): array
    {
        return array_map(
            'trim',
            explode(',', spl_autoload_extensions())
        );
    }

    /**
     * 拡張子を追加(重複は無視)
     */
    public function add(string $extension): self
    {
        $extension = '.' . ltrim($extension, '.');
        $current   = $this->getExtensions();

        if (!in_array($extension, $current, true)) {
            $current[] = $extension;
            $this->apply($current);
        }

        return $this;
    }

    /**
     * 拡張子を削除
     */
    public function remove(string $extension): self
    {
        $extension = '.' . ltrim($extension, '.');
        $filtered  = array_filter(
            $this->getExtensions(),
            fn($e) => $e !== $extension
        );

        $this->apply(array_values($filtered));
        return $this;
    }

    /**
     * 指定した拡張子のみに設定(上書き)
     *
     * @param list<string> $extensions
     */
    public function set(array $extensions): self
    {
        $normalized = array_map(
            fn($e) => '.' . ltrim($e, '.'),
            $extensions
        );
        $this->apply($normalized);
        return $this;
    }

    /**
     * 初期値に戻す
     */
    public function restore(): self
    {
        spl_autoload_extensions($this->original);
        $this->current = $this->original;
        return $this;
    }

    public function has(string $extension): bool
    {
        $extension = '.' . ltrim($extension, '.');
        return in_array($extension, $this->getExtensions(), true);
    }

    public function dump(): void
    {
        echo "現在の拡張子: " . spl_autoload_extensions() . "\n";
        echo "初期値:       {$this->original}\n";
    }

    private function apply(array $extensions): void
    {
        $str           = implode(',', $extensions);
        $this->current = spl_autoload_extensions($str);
    }
}

// --- 使用例 ---
$manager = new AutoloadExtensionManager();
$manager->dump();

echo "\n--- 追加 ---\n";
$manager->add('.class.php')->add('.inc.php')->add('.php'); // .phpは既存のため無視
$manager->dump();

echo "\n--- 削除 ---\n";
$manager->remove('.inc'); // レガシー拡張子を削除
$manager->dump();

echo "\n--- 存在確認 ---\n";
echo ".php:       " . ($manager->has('.php')       ? 'yes' : 'no') . "\n";
echo ".inc:       " . ($manager->has('.inc')       ? 'yes' : 'no') . "\n";
echo ".class.php: " . ($manager->has('.class.php') ? 'yes' : 'no') . "\n";

echo "\n--- 上書き設定 ---\n";
$manager->set(['.php']);
$manager->dump();

echo "\n--- 復元 ---\n";
$manager->restore();
$manager->dump();

実行結果:

現在の拡張子: .inc,.php
初期値:       .inc,.php

--- 追加 ---
現在の拡張子: .inc,.php,.class.php,.inc.php
初期値:       .inc,.php

--- 削除 ---
現在の拡張子: .php,.class.php,.inc.php
初期値:       .inc,.php

--- 存在確認 ---
.php:       yes
.inc:       no
.class.php: yes

--- 上書き設定 ---
現在の拡張子: .php
初期値:       .inc,.php

--- 復元 ---
現在の拡張子: .inc,.php
初期値:       .inc,.php

解説: 初期値を退避し、restore() で確実に戻せる設計です。テストや一時的な設定変更時に安全に利用できます。


サンプル4:テスト前後の設定保護

テスト実行中だけ設定を変更し、終了後に確実に元に戻すパターンです。

<?php
declare(strict_types=1);

/**
 * テスト用:拡張子設定を一時変更するスコープクラス
 */
class ExtensionScope
{
    private string $previous;

    public function __construct(string $extensions)
    {
        $this->previous = spl_autoload_extensions();
        spl_autoload_extensions($extensions);
        echo "[Scope] 設定変更: {$this->previous} → {$extensions}\n";
    }

    public function restore(): void
    {
        spl_autoload_extensions($this->previous);
        echo "[Scope] 設定復元: " . spl_autoload_extensions() . "\n";
    }

    public function __destruct()
    {
        $this->restore();
    }

    /**
     * クロージャのスコープ内だけ設定を変更する
     */
    public static function run(string $extensions, callable $callback): mixed
    {
        $scope = new self($extensions);
        try {
            return $callback();
        } finally {
            $scope->restore();
        }
    }
}

// --- 使用例 ---

echo "テスト前: " . spl_autoload_extensions() . "\n\n";

// スコープ内だけ .php に限定
$result = ExtensionScope::run('.php', function () {
    echo "スコープ内: " . spl_autoload_extensions() . "\n";

    // この範囲内でのオートロードは .php のみ検索
    spl_autoload_register('spl_autoload');

    // ... テスト処理 ...
    return '処理完了';
});

echo "\nテスト後: " . spl_autoload_extensions() . "\n";
echo "結果: {$result}\n";

実行結果:

テスト前: .inc,.php

[Scope] 設定変更: .inc,.php → .php
スコープ内: .php
[Scope] 設定復元: .inc,.php
[Scope] 設定復元: .inc,.php

テスト後: .inc,.php
結果: 処理完了

解説: try-finally とデストラクタを組み合わせることで、例外が発生しても確実に設定が元に戻ります。グローバル設定を一時的に変える際のベストプラクティスです。


サンプル5:拡張子設定とカスタムハンドラの組み合わせ

spl_autoload_extensions() を参照するカスタムハンドラを実装するパターンです。

<?php
declare(strict_types=1);

/**
 * spl_autoload_extensions() の設定を尊重するカスタムローダー
 *
 * 注意: spl_autoload() 自体はこの設定を参照するが、
 *       カスタムハンドラは自分で参照ロジックを実装する必要がある
 */
class ExtensionAwareLoader
{
    public function __construct(
        private readonly string $baseDir
    ) {}

    public function register(): void
    {
        spl_autoload_register([$this, 'load']);
    }

    public function load(string $class): bool
    {
        // spl_autoload_extensions() の設定を動的に参照
        $extensions = array_map(
            'trim',
            explode(',', spl_autoload_extensions())
        );

        $relativePath = str_replace('\\', DIRECTORY_SEPARATOR, $class);

        foreach ($extensions as $ext) {
            $file = $this->baseDir . DIRECTORY_SEPARATOR . $relativePath . $ext;
            echo "[Loader] 試行: {$file}\n";

            if (file_exists($file)) {
                require $file;
                echo "[Loader] ✅ ロード成功: {$file}\n";
                return true;
            }
        }

        return false;
    }
}

// --- 設定と登録 ---
spl_autoload_extensions('.php,.class.php');
echo "拡張子: " . spl_autoload_extensions() . "\n\n";

$loader = new ExtensionAwareLoader(__DIR__ . '/src');
$loader->register();

// クラスを参照するとオートロードが発火
// new App\Service\CacheService();

// 拡張子を変更するとローダーの試行順が変わる
spl_autoload_extensions('.class.php,.php');
echo "変更後の拡張子: " . spl_autoload_extensions() . "\n";

// new App\Service\CacheService(); // 今度は .class.php を先に試す

実行結果(ファイルが存在する場合):

拡張子: .php,.class.php

変更後の拡張子: .class.php,.php

解説: カスタムハンドラは spl_autoload_extensions() を自動参照しないため、参照したい場合は explode(',', spl_autoload_extensions()) で自分で読み取ります。こうすることで、拡張子設定の変更がカスタムハンドラにも反映されます。


サンプル6:プロジェクト初期化時の一元設定

アプリケーション起動時に拡張子設定を含むオートロード全体を初期化するパターンです。

<?php
declare(strict_types=1);

/**
 * アプリケーションのオートロード設定を一元管理するブートストラップクラス
 */
final class AutoloadBootstrap
{
    private static bool $initialized = false;

    /**
     * オートロードを初期化する(2回目以降は無視)
     *
     * @param array{
     *   extensions: list<string>,
     *   src_dirs: list<string>,
     *   namespace_map: array<string, string>
     * } $config
     */
    public static function init(array $config): void
    {
        if (self::$initialized) {
            return;
        }

        // 1. 拡張子を設定
        $extensions = implode(',', array_map(
            fn($e) => '.' . ltrim($e, '.'),
            $config['extensions']
        ));
        spl_autoload_extensions($extensions);
        echo "[Bootstrap] 拡張子: {$extensions}\n";

        // 2. include_path を設定
        $paths = array_filter($config['src_dirs'], 'is_dir');
        if (!empty($paths)) {
            set_include_path(
                implode(PATH_SEPARATOR, array_merge(['.'], $paths))
            );
            echo "[Bootstrap] include_path: " . implode(', ', $paths) . "\n";
        }

        // 3. 名前空間マップベースのカスタムハンドラを登録
        $namespaceMap = $config['namespace_map'];
        spl_autoload_register(function (string $class) use ($namespaceMap): void {
            foreach ($namespaceMap as $prefix => $dir) {
                $prefix = rtrim($prefix, '\\') . '\\';
                if (!str_starts_with($class, $prefix)) {
                    continue;
                }

                $relative = str_replace(
                    ['\\', $prefix],
                    [DIRECTORY_SEPARATOR, ''],
                    substr($class, strlen($prefix))
                );

                foreach (array_map('trim', explode(',', spl_autoload_extensions())) as $ext) {
                    $file = rtrim($dir, '/') . '/' . $relative . $ext;
                    if (file_exists($file)) {
                        require $file;
                        return;
                    }
                }
            }
        });

        // 4. フォールバックとして spl_autoload を登録
        spl_autoload_register('spl_autoload');

        self::$initialized = true;
        echo "[Bootstrap] 登録ハンドラ数: " . count(spl_autoload_functions()) . "\n";
        echo "[Bootstrap] 初期化完了\n";
    }

    public static function status(): void
    {
        echo "\n=== Autoload Status ===\n";
        echo "拡張子:       " . spl_autoload_extensions() . "\n";
        echo "include_path: " . get_include_path() . "\n";
        echo "ハンドラ数:   " . count(spl_autoload_functions()) . "\n";
    }
}

// --- アプリケーション起動時に1回だけ呼ぶ ---
AutoloadBootstrap::init([
    'extensions'    => ['php'],                     // .php のみ
    'src_dirs'      => [
        __DIR__ . '/src',
        __DIR__ . '/lib',
    ],
    'namespace_map' => [
        'App\\'    => __DIR__ . '/src',
        'Tests\\'  => __DIR__ . '/tests',
        'Vendor\\' => __DIR__ . '/vendor/src',
    ],
]);

AutoloadBootstrap::status();

// 2回目は無視される
AutoloadBootstrap::init(['extensions' => ['inc'], 'src_dirs' => [], 'namespace_map' => []]);
echo "\n(2回目の init は無視される)\n";
echo "拡張子変わらず: " . spl_autoload_extensions() . "\n";

実行結果:

[Bootstrap] 拡張子: .php
[Bootstrap] include_path: /var/www/project/src, /var/www/project/lib
[Bootstrap] 登録ハンドラ数: 2
[Bootstrap] 初期化完了

=== Autoload Status ===
拡張子:       .php
include_path: .:/var/www/project/src:/var/www/project/lib
ハンドラ数:   2

(2回目の init は無視される)
拡張子変わらず: .php

解説: 拡張子・include_path・名前空間マップ・ハンドラ登録を一括で行うブートストラップクラスです。$initialized フラグで二重初期化を防いでいます。実際のフレームワークの起動処理に近いパターンです。


よくある落とし穴

① カスタムハンドラには自動で適用されない

spl_autoload_extensions('.myext');

// ❌ このカスタムハンドラは .myext を知らない
spl_autoload_register(function (string $class): void {
    $file = __DIR__ . '/src/' . $class . '.php'; // ← 拡張子をハードコード
    if (file_exists($file)) {
        require $file;
    }
});

// ✅ 動的に参照する
spl_autoload_register(function (string $class): void {
    foreach (explode(',', spl_autoload_extensions()) as $ext) {
        $file = __DIR__ . '/src/' . $class . trim($ext);
        if (file_exists($file)) {
            require $file;
            return;
        }
    }
});

② 設定はグローバルで上書きされる

spl_autoload_extensions('.php');

// サードパーティライブラリやプラグインがこれを上書きするかもしれない
some_library_init(); // 内部で spl_autoload_extensions('.php,.inc') を呼ぶかも

// ✅ 自分の設定を最後に適用する、またはカスタムハンドラで拡張子を管理する

③ 拡張子の前のドットを忘れない

// ❌ ドットなし
spl_autoload_extensions('php,inc');
// → ".php" や ".inc" では検索されない

// ✅ ドットあり
spl_autoload_extensions('.php,.inc');

④ 設定はスレッドセーフではない(CLI並列処理)

// CLIでpcntl_fork()やFiberを使う場合、
// グローバル設定の変更が他の処理に影響することがある
// → 可能な限りカスタムハンドラで拡張子をハードコードする

まとめ

ポイント内容
主な役割spl_autoload() が検索するファイル拡張子の設定・取得
デフォルト.inc,.php
影響範囲spl_autoload() のみ(カスタムハンドラには自動適用されない)
現代の推奨.php のみに絞る(.inc はレガシー)
安全な変更変更前に退避し try-finally または restore() で元に戻す
複数設定カンマ区切りで指定。順序が検索優先順になる

spl_autoload_extensions() 単体はシンプルな設定関数ですが、spl_autoload() と組み合わせることでオートロードの挙動を細かく制御できます。カスタムハンドラを使う場合は、この設定を動的に参照するかどうかを意識的に設計することが、保守性の高いオートロード実装につながります。


PHP 8.x / 執筆時点の最新安定版にて動作確認済み

タイトルとURLをコピーしました