WordPressでautoloadを作成する

WordPressでautoloadを作成する

ファイルはクラス単位で分割したいけどファイルの読み込みにいちいち「require_once」は面倒くさい場合の解決策をご紹介します。

プラグインの「Google Material Design」に含まれていたコードでライセンスは「Apache License 2.0」でした。

autoload用の抽象クラス作成

まずはコードから見ていきましょう。

namespace Sample\Plugin;

abstract class Plugin_Base {
	public $slug;

	public $dir_path;

	public $dir_url;
  
  // 読み込み対象のフォルダ
	protected $autoload_class_dir = 'includes';

	protected $autoload_matches_cache = [];

  public function __construct() {
    $location       = $this->locate_plugin();
		$this->slug     = $location['dir_basename'];
		$this->dir_path = $location['dir_path'];
		$this->dir_url  = $location['dir_url'];
		spl_autoload_register( [ $this, 'autoload' ] );
  }

	public function get_object_reflection() {
		static $reflection;
		if ( empty( $reflection ) ) {
			// @codeCoverageIgnoreStart
			$reflection = new \ReflectionObject( $this );
			// @codeCoverageIgnoreEnd
		}

		return $reflection;
	}

	public function relative_path( $path, $start, $sep ) {
		$path = explode( $sep, untrailingslashit( $path ) );
		if ( count( $path ) > 0 ) {
			foreach ( $path as $p ) {
				array_shift( $path );
				if ( $p === $start ) {
					break;
				}
			}
		}

		return implode( $sep, $path );
	}

	public function locate_plugin() {
		$file_name = $this->get_object_reflection()->getFileName();

		// Windows compat.
		if ( '/' !== \DIRECTORY_SEPARATOR ) {
			// @codeCoverageIgnoreStart
			$file_name = str_replace( \DIRECTORY_SEPARATOR, '/', $file_name );
			// @codeCoverageIgnoreEnd
		}

		$plugin_dir  = dirname( dirname( $file_name ) );
		$plugin_path = $this->relative_path( $plugin_dir, basename( content_url() ), '/' );

		$dir_url      = content_url( trailingslashit( $plugin_path ) );
		$dir_path     = $plugin_dir;
		$dir_basename = basename( $plugin_dir );

		return compact( 'dir_url', 'dir_path', 'dir_basename' );
	}

	public function autoload( $class ) {
		if ( ! isset( $this->autoload_matches_cache[ $class ] ) ) {
			if ( ! preg_match( '/^(?P<namespace>.+)\\\\(?P<class>[^\\\\]+)$/', $class, $matches ) ) {
				$matches = false;
			}

			$this->autoload_matches_cache[ $class ] = $matches;
		} else {
			$matches = $this->autoload_matches_cache[ $class ];
		}

		if ( empty( $matches ) ) {
			return;
		}

		$namespace = $this->get_object_reflection()->getNamespaceName();

		if ( strpos( $matches['namespace'], $namespace ) === false ) {
			return;
		}

		$class_name = $matches['class'];
		$class_path = \trailingslashit( $this->dir_path );

		if ( $this->autoload_class_dir ) {
			$class_path .= \trailingslashit( $this->autoload_class_dir );

			$sub_path = str_replace( $namespace . '\\', '', $matches['namespace'] );
			if ( ! empty( $sub_path ) && 'Sample\Plugin' !== $sub_path ) {
				$class_path .= str_replace( '\\-', '/', strtolower( preg_replace( '/(?<!^)([A-Z])/', '-\\1', $sub_path ) ) . '/' );
			}
		}

		$class_path .= sprintf( 'class-%s.php', strtolower( str_replace( '_', '-', $class_name ) ) );

		if ( is_readable( $class_path ) ) {
			require_once $class_path;
		}
	}
}

まず、「コンストラクタ」です。

自クラスのメンバー変数に値をセットし、「spl_autoload_register」でメソッドのautoloadを__autoload()として実装しています。

続いて、「get_object_reflection」「relative_path」「locate_plugin」です。

「get_object_reflection」では自クラスのReflectionObjectインスタンスを作成して、返してくれます。

「$reflection」変数が関数内でstaic宣言されているので、関数実行後も値が保持されます。

「relative_path」は名前の通り相対パスを返してくれる関数です。

引数に「パス」「開始位置」「セパレーター」を取ります。

例) 「https://kumatech-lab.com/wp-content/test/sample」「wp-content」「/」

→「wp-content/sapmle」

「locate_plugin」はプラグインディレクトリ名、プラグインディレクトリURL、プラグインディレクトリパスを返してくれます。

上のコードではファイルのディレクトリの親ディレクトリの取得をしています。作成方法によって変更してください。

例)「sample-plugin/includes/class-plugin-base.php」⇒「sample-plugin」

続いて「autoload」です。

引数のクラスにはクラス名が入ってきます。

そのクラス名から名前空間とクラス名を分離し、クラス名をもとに読み込むファイルパスを作成。

そのファイルパスを読み込むという形です。

※$autoload_class_dirは読み込みたいディレクトリ。

autoload用の抽象クラスを継承して具体クラスを作成する

まずはコードからです。

namespace Sample\Plugin;

class Plugin extends Plugin_Base {

    /**
     * コンストラクタ
     */
    public function init() {
        new Sample();
        new Test();
        new Example();
        add_action( 'wp_enqueue_scripts', [ $this, ... ] );
    }
}

継承するだけですね。オーバーライドなどは必要ないです。

「__construct」で定義すると親クラスのコンストラクタをオーバーライドしてしまいますので、別のメソッドとして実行するか、明示的に継承元のコンストラクタを実行してください。

上のコードを実行すると、

  • 「includes/class-sample.php」
  • 「includes/class-test.php」
  • 「includes/class-example.php」

の3つが自動で読み込まれます。

親クラスでディレクトリのURLもパスも持っているので、同時にスタイルやスクリプトを読み込んだりするのに便利です。

まとめ

以上、プラグインを作成する際、テーマを作成する際に読み込み地獄を回避できるautoloadの作成でした。

コード自体はわかりやすいと思いますので、わからない関数が出てきた場合には一つずつ調べてみてください。

元コードの方には「add_action」「add_filter」のラッパー関数なども用意されていましたので気になる方は以下プラグインを見てください。