Автозагрузка классов для WordPress

Если вы используете у себя в проектах сотни require или include, то вам точно стоит прочесть эту статью.

Зачем нужна автозагрузка?

Автозагрузка (autoload) нужна для того, чтобы навсегда избавится от require, include и постоянного изменения порядка их подключения.

Рассмотрим два вариант autoload:

  • composer
  • spl_autoload

Autoload своих классов через composer

В файл composer.json нужно добавить директиву autoload и в нее classmap с перечнем папок, в которых нужно искать классы, интерфейсы и прочее.

{
  ...
  "autoload": {
    "classmap": [
      "folder1",
      "folder2"
    ]
  }
  ...
}

После этого нужно обязательно обновить autoload composer’а с помощью следующей команды:

composer dump-autoload

Или

composer dumpautoload

После этого в /vendor/composer/autoload_classmap.php появляется массив ключ=значение, где ключ — это полное название класса, а значение — это путь к данному классу. Сам файл выглядит примерно так:

<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'My_Namespace\Example1' => $baseDir . '/folder1/class-example1.php',
    'My_Namespace\Example2' => $baseDir . '/folder2/class-example2.php',
);

Свой автозагрузчик с помощью spl_autoload

Иногда проект может быть достаточно большой включать в себя плагины, mu-плагины и темы. При работе с мультисайтом/ами вполне возможна такая ситуация.

Столкнувшись с такой проблемой, решил сделать небольшой mu-плагин, который будет подгружать все нужные мне файлы сам. Решил отойти от composer’а, чтобы не тянуть его везде, где он мне нужен.

Меняем namespace на путь к файлу

Следующий пример написан с соблюдением WPCS:

class Autoload {

	private $prefix = 'My_Namespace';
	public function __construct() {
		spl_autoload_register( [ $this, 'autoload' ] );
	}

	private function autoload( string $class ): void {
		if ( 0 === strpos( $class, $this->prefix ) ) {
			$plugin_parts = explode( '\', $class );
			$name         = array_pop( $plugin_parts );
			$name         = preg_match( '/^(Interface|Trait)/', $name )
				? $name . '.php'
				: 'class-' . $name . '.php';
			$path         = implode( '/', $plugin_parts ) . '/' . $name;
			$path         = strtolower( str_replace( [ '\', '_' ], [ '/', '-' ], $path ) );
			$path         = WP_CONTENT_DIR . '/plugins/' . $path;
			require_once $path;
		}
	}

}

new Autoload();

С помощью ф-ции spl_autoload_register мы добавляем autoload, который будет срабатывать каждый раз, когда вызывается неизвестная ф-ция, класс или интерфейс.

Обязательно проверяем на то, чтобы все классы начинались с $this->prefix, который в примере My_Namespace. Затем формируем нужный путь к файлу и подключаем его. Все вроде бы хорошо, но есть одна проблема с тем, что при большом кол-ве классов слишком много действий вместо просто подключения файлов. Для этого нужно сделать механизм кеширования. Попробуем сделать что-то, вроде classmap от composer’а.

Classmap для spl_autoload

class Autoload {

	private $map_file;
	private $map;
	private $prefix = 'My_Namespace';
	private $has_been_update = false;

	public function __construct() {
		$this->map_file = __DIR__ . '/classmap.php';
		$this->map = @include $this->map_file;
		$this->map = is_array( $this->map ) ? $this->map : [];
		spl_autoload_register( [ $this, 'autoload' ] );
		add_action( 'shutdown', [ $this, 'update_cache' ] );
	}

	private function autoload( string $class ): void {
		if ( 0 === strpos( $class, $this->prefix ) ) {
			if ( $this->map[ $class ] && file_exists( $this->map[ $class ] ) ) {
				require_once $this->map[ $class ];
			} else {
				$this->has_been_update = true;
				$plugin_parts          = explode( '\', $class );
				$name                  = array_pop( $plugin_parts );
				$name                  = preg_match( '/^(Interface|Trait)/', $name )
					? $name . '.php'
					: 'class-' . $name . '.php';
				$path                  = implode( '/', $plugin_parts ) . '/' . $name;
				$path                  = strtolower( str_replace( [ '\', '_' ], [ '/', '-' ], $path ) );
				$path                  = WP_CONTENT_DIR . '/plugins/' . $path;
				$this->map[ $class ] = $path;
				require_once $path;
			}
		}
	}
	
	public function update_cache(): void {
		if ( ! $this->has_been_update ) {
			return;
		}
		$map = implode(
			"n",
			array_map(
				function ( $k, $v ) {
					return "'$k' => '$v',";
				},
				array_keys( $this->map ),
				array_values( $this->map )
			)
		);
		
		file_put_contents( $this->map_file, '<?php return [' . $map . '];' );
	}

}

Добавляем 3 свойства:

  • $map_file — путь к файлу classmap
  • $map — classmap
  • $has_been_update — свойство, которое проверяет обновился ли classmap с последней загрузки страницы.

Метод autoload немного поменялся. Вот основные отличия:

if ( $this->map[ $class ] && file_exists( $this->map[ $class ] ) ) {
	// Подключаем файл, который мы нашли в classmap. Проверка file_exists нужна на случай, если мы захотим удалить, переместить или переименовать файл.
} else {
	$this->has_been_update = true; // classmap нужно обновить
	...
	$this->map[ $class ] = $path; // Обновляем classmap
	...
}

В метод update_cache, который срабатывает на событие shutdown обновляем сам файл classmap’а, если он был изменен с последней загрузки.

Полный пример кода с поддержкой WPCS можно посмотреть на github’e: https://github.com/mdenisenko/WP-Autoload

Добавить комментарий

%d такие блоггеры, как: