Если вы используете у себя в проектах сотни 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