2026年9月12日

2026年9月12日

WordPressプラグインをPSR-4オートロードで整理する:ディレクトリ構成と設計の手順

WordPressプラグインをPSR-4のルールで組み立てると、「クラス名を見ればファイルの場所が分かる」状態になり、require_once の羅列をなくせます。やることは、namespaceとディレクトリの対応を決め、1ファイルに1クラスを置き、オートローダー(Composerか自前の関数)に対応を教えることです。この記事では、実際のディレクトリ構成例、Composerを使う場合と使わない場合のオートローダー、本体ファイルを薄く保つ設計、よくある「Class not found」の原因までを説明します。

namespaceの基本的な書き方(フックへのコールバック指定など)や、Composerでの外部ライブラリの管理は別記事で扱い、ここではプラグインの構造づくりに絞ります。

PSR-4のルールを押さえる

PSR-4は、PHPの標準化団体PHP-FIGが定めたオートロードの規約です。ルールは単純で、namespaceの先頭部分(プレフィックス)を1つのディレクトリに対応させ、残りの部分をサブディレクトリとファイル名に置き換えます。

たとえば Acme\BookShelf\ を src/ に対応させると、次のようになります。

  • Acme\BookShelf\Plugin → src/Plugin.php
  • Acme\BookShelf\Admin\SettingsPage → src/Admin/SettingsPage.php
  • Acme\BookShelf\Rest\BooksController → src/Rest/BooksController.php

ファイル名はクラス名と同じで、大文字・小文字まで一致させます。1ファイルには1つのクラス(またはインターフェース、トレイト)だけを置きます。

ディレクトリ構成の例

book-shelf/
├── book-shelf.php          … プラグイン本体(ヘッダーと起動処理だけ)
├── uninstall.php           … 削除時の後片付け
├── composer.json
├── src/
│   ├── Plugin.php          … 各機能を組み立てて登録する
│   ├── Admin/
│   │   └── SettingsPage.php
│   ├── Frontend/
│   │   └── Shortcode.php
│   ├── Rest/
│   │   └── BooksController.php
│   └── PostType/
│       └── Book.php
├── templates/              … 表示用のテンプレート(クラスではない)
├── assets/                 … CSS・JavaScript・画像
├── languages/              … 翻訳ファイル
└── tests/

ポイントは、クラスを置く src/ と、クラスではないファイル(テンプレートやアセット)を分けることです。オートロードの対象はクラスだけなので、テンプレートは従来どおり include で読み込みます。

フォルダの分け方は、「管理画面」「表側」「REST API」のように、WordPressのどの場面で動くかで分けると、どこを触れば良いか迷いにくくなります。

WordPressのファイル命名規約との違い

WordPressのコーディング規約では、クラスのファイル名を class-settings-page.php のように小文字とハイフンで書くことになっています。PSR-4ではファイル名がクラス名そのもの(SettingsPage.php)になるため、両者は両立しません。

PHP_CodeSnifferでWordPress Coding Standards(WPCS)を使っている場合、src/ 配下ではファイル名に関するチェックを除外する設定を入れるのが一般的です。除外するルール名や書き方はWPCSのバージョンで変わることがあるため、使っている版のドキュメントで確認してください(2026年10月現在)。どちらの規約に合わせるかをプロジェクトの最初に決め、混在させないことが大切です。既存の class-*.php 形式のコードをすぐに直せない場合は、Composerの classmap 方式で読み込む手もあります。

ステップ1:Composerでオートロードを設定する

Composerを使えるなら、composer.json に対応を書くだけでオートローダーが用意されます。

{
    "name": "acme/book-shelf",
    "type": "wordpress-plugin",
    "autoload": {
        "psr-4": {
            "Acme\\BookShelf\\": "src/"
        }
    }
}

設定を書いたら composer dump-autoload を実行します。vendor/autoload.php が生成されるので、本体ファイルから読み込みます。PSR-4の対応を一度登録すれば、src/ にクラスを追加するたびにコマンドを打ち直す必要はありません(--classmap-authoritative で最適化したビルドは例外で、作り直しが必要です)。

ステップ1(別案):Composerを使わずに自前で書く

依存ライブラリがなく、Composerを導入したくない場合は、spl_autoload_register() で同じ動きを書けます。

<?php
namespace Acme\BookShelf;

spl_autoload_register( function ( string $class ): void {
    $prefix = __NAMESPACE__ . '\\';
    $base   = __DIR__ . '/src/';

    // 自分のnamespace以外は担当しない
    if ( 0 !== strpos( $class, $prefix ) ) {
        return;
    }

    $relative = substr( $class, strlen( $prefix ) );
    $file     = $base . str_replace( '\\', '/', $relative ) . '.php';

    if ( is_readable( $file ) ) {
        require $file;
    }
} );

自分のnamespace以外のクラス名が来たら何もせずに返すことが重要です。他のプラグインのオートローダーも同じ仕組みで順番に呼ばれるため、無関係なクラスまで探しにいくと、余計なファイルアクセスやエラーの原因になります。

ステップ2:本体ファイルは起動だけにする

プラグイン本体のファイルには、ヘッダー、オートローダーの読み込み、起動の呼び出しだけを書きます。処理を本体ファイルに書き足していくと、結局は構造が崩れていきます。

<?php
/**
 * Plugin Name: Book Shelf
 * Requires PHP: 7.4
 * Text Domain: book-shelf
 */

namespace Acme\BookShelf;

defined( 'ABSPATH' ) || exit;

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

const PLUGIN_FILE = __FILE__;

register_activation_hook( __FILE__, array( PostType\Book::class, 'activate' ) );

add_action( 'plugins_loaded', function () {
    ( new Plugin() )->register();
} );

有効化フックは本体ファイルで登録する必要があります。plugins_loaded の中で登録しても、有効化の処理には間に合わないためです。ここで登録している activate は、カスタム投稿タイプを登録してから flush_rewrite_rules() を呼ぶ静的メソッドを想定しています。

ステップ3:機能を組み立てるクラスを作る

Plugin クラスは、各機能のクラスを作って、それぞれのフック登録を呼び出す役だけを持たせます。

<?php
namespace Acme\BookShelf;

final class Plugin {
    public function register(): void {
        ( new PostType\Book() )->register();
        ( new Frontend\Shortcode() )->register();
        ( new Rest\BooksController() )->register();

        if ( is_admin() ) {
            ( new Admin\SettingsPage() )->register();
        }
    }
}

各クラスは register() の中で自分に必要なフックだけを登録します。コンストラクタでフックを登録しないようにしておくと、テストでクラスを単体で使いやすくなります。管理画面だけで使うクラスを is_admin() で分けておくと、表側では読み込まれず、オートロードの利点が活きます。

設定ページや REST API のクラスでは、構造を整えても中身の安全対策は省けません。current_user_can() による権限チェック、nonceの検証、入力のサニタイズと出力のエスケープは、各クラスの中で従来どおり行います。

「Class not found」の原因の調べ方

構造を変えた直後に最も多いエラーです。可能性の高い順に確認します。

  1. 大文字・小文字の違い: WindowsやmacOSの開発環境ではファイル名の大文字・小文字が区別されないことが多く、Linuxの本番サーバーでだけエラーになります。settingsPage.php と SettingsPage.php の違いを見直してください
  2. namespaceの宣言とフォルダの不一致: src/Admin/SettingsPage.php の中の宣言が namespace Acme\BookShelf\Admin; になっているかを確認します
  3. composer.jsonの区切り文字: JSONの中では "Acme\\BookShelf\\" のようにバックスラッシュを重ね、末尾にも付けます
  4. オートロードの再生成忘れ: 対応の設定を変えた後や、最適化ビルドの後は composer dump-autoload が必要です
  5. use の書き忘れ: 別のサブnamespaceのクラスを使うときは use かnamespace付きの名前で指定します

WP_DEBUG_LOG を有効にすると、エラーに探されたクラスのnamespace付きの名前が記録されます。その名前から、オートローダーがどのファイルを探したかを逆算すると原因を絞り込めます。作業はローカルかステージングで行い、本番へ上げる前に一度Linux環境で動作を確かめてください。

まとめ

  • PSR-4は「namespaceのプレフィックス=1つのディレクトリ」「クラス名=ファイル名」の規約
  • src/ にクラスを、templates/ や assets/ にクラス以外を置いて分ける
  • オートローダーはComposerの autoload.psr-4 か、自前の spl_autoload_register() で用意する
  • 本体ファイルは起動だけにし、各機能は register() でフックを登録する
  • 「Class not found」はまず大文字・小文字とnamespace宣言の一致を確認する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲