Как это ни удивительно, но на вопрос об автоматическом загрузчике классов PHP большинство ответит, что стандартная функция
__autoload() — это гибкое и надежное решение. Давайте попытаемся разобраться, так ли это на самом деле.
Будем использовать следующие понятия:
автозагрузчик — функция или метод, реализующие логику автоматического поиска и загрузки скрипта с объявлением класса по его имени;
стек автозагрузки — массив, используемый системой, для хранения всех автозагрузчиков в порядке их добавления;
SPL —
стандартная библиотека PHP.
Стандартная функция __autoload()
Стандартная функция
__autoload() появилась в
PHP5 и была предназначена для автоматической загрузки скриптов с объявлениями классов, вместо бесконечных инструкций
require(). Несмотря на преимущества, предоставляемые этой функцией, она имеет ряд существенных недостатков:
для регистрации нескольких автозагрузчиков необходимо весь код помещать в тело единственной функции (вынесение кода во вспомогательные функции/классы не отменяет того факта, что основная логика должна храниться в теле функции
__autoload());
нет возможности динамически активировать/деактивировать автозагрузчики.
В борьбе с этими ограничениями на помощь приходят стандартные функции из библиотеки
SPL, появившиеся в
PHP 5.1.2. И тот факт, что событие это произошло 12 июля 2006 года, не оставляет убедительных причин не использовать именно их вместо
__autoload(), которая
уже переведена в разряд discouraged и вскоре станет устаревшей.
SPL-функции для автоматической загрузки классов
SPL содержит 6(!) функций, связанных с автоматической загрузкой классов:
spl_autoload_call — принудительно загружает класс по его имени, используя все доступные в системе автозагрузчики;
spl_autoload_extensions — возвращает/модифицирует расширения файлов, из которых происходит загрузка неинициализированных классов;
spl_autoload_functions — возвращает список всех зарегистрированных автозагрузчиков в системе;
spl_autoload_register — регистрация собственного автозагрузчика в стеке автозагрузки;
spl_autoload_unregister — удаление автозагрузчика из стека автозагрузки;
spl_autoload — основная функция автоматической загрузки классов. Именно она вызывается при обращении к классу, который еще не инициализирован. Данная функция активирует все автоматические загрузчики из стека в порядке их добавления.
Чтобы избежать ограничений, которые накладывает функция
__autoload(), в PHP 5.1.2 было введено понятие стека автозагрузки. Текущее состояние стека можно просмотреть функцией
spl_autoload_functions(). Изначально стек не инициализирован, и автоматический поиск скриптов с объявлением неинициализированных классов не производится. Инициализация стека происходит при определении функции
__autoload() или при регистрации собственных автозагрузчиков, используя
spl_autoload_register(). Необходимо помнить, что возможна регистрация нескольких автозагрузчиков при помощи функции
spl_autoload_register(), но ее первое использование производит переинициализацию стека, и обработчик
__autoload(), если он уже был зарегистрирован, будет безвозвратно удален.
Немного практики
В своем коде я буду использовать 2 вспомогательных скрипта:
autoload.php для объявления функции
__autoload();
Autoloader.php для объявления класса
Autoloader с собственными методами автоматической загрузки классов.
Файл autoload.php
Объявление функции
__autoload() вынесено в отдельный файл, чтобы предотвратить автоматическую регистрацию в системе этого автозагрузчика. Вместо этого он будет загружен при помощи функции
require(), когда понадобится.
<?php
function __autoload($className) {
// Some autoload code goes here...
}
Файл Autoloader.php
В данном классе содержатся 2 статических метода, которые будут выполнять роль автозагрузчиков. Метод
loadPackages загружает скрипт из файловой системы, а
loadPackagesAndLog выполняет те же операции и дополнительно логирует свои действия.
<?php
class Autoloader
{
private static $_lastLoadedFilename;
public static function loadPackages($className)
{
$pathParts = explode('_', $className);
self::$_lastLoadedFilename = implode(DIRECTORY_SEPARATOR, $pathParts) . '.php';
require_once(self::$_lastLoadedFilename);
}
public static function loadPackagesAndLog($className)
{
self::loadPackages($className);
printf("Class %s was loaded from %sn", $className, self::$_lastLoadedFilename);
}
}
Тестирование стека автозагрузки
Cтек автозагрузки изначально не инициализирован
var_dump(spl_autoload_functions());
// Output:
// ---------
bool(false)
Как видно из вывода, стек автозагрузки пуст. Если произойдет обращение к классу, который не объявлен, скрипт завершает работу с ошибкой.
Регистрируем функцию __autoload() и убеждаемся, что она была помещается в стек автозагрузки
require_once('autoload.php');
var_dump(spl_autoload_functions());
// Output:
// ---------
array(1) {
[0]=>
string(10) "__autoload"
}
Регистрируем собственный автозагрузчик средствами spl_autoload_register()
require_once('Autoloader.php');
spl_autoload_register(array('Autoloader', 'loadPackages'));
var_dump(spl_autoload_functions());
// Output:
// ---------
array(1) {
[0]=>
array(2) {
[0]=>
string(10) "Autoloader"
[1]=>
string(12) "loadPackages"
}
}
Загрузчик
Autoloader::loadPackages помещается в стек автозагрузки и замещает собой предварительно зарегистрированную функцию
__autoload.
Добавляем еще один автозагрузчик средствами spl_autoload_register()
spl_autoload_register(array('Autoloader', 'loadPackagesAndLog'));
var_dump(spl_autoload_functions());
// Output:
// ---------
array(2) {
[0]=>
array(2) {
[0]=>
string(10) "Autoloader"
[1]=>
string(12) "loadPackages"
}
[1]=>
array(2) {
[0]=>
string(10) "Autoloader"
[1]=>
string(18) "loadPackagesAndLog"
}
}
Новый загрузчик
Autoloader::loadPackagesAndLog помещается в конец стека.
Убеждаемся, что функция spl_autoload_register() достаточно разумна, чтобы не добавлять один и тот же автозагрузчик дважды
spl_autoload_register(array('Autoloader', 'loadPackagesAndLog'));
var_dump(spl_autoload_functions());
// Output:
// ---------
array(2) {
[0]=>
array(2) {
[0]=>
string(10) "Autoloader"
[1]=>
string(12) "loadPackages"
}
[1]=>
array(2) {
[0]=>
string(10) "Autoloader"
[1]=>
string(18) "loadPackagesAndLog"
}
}
Загрузчик
Autoloader::loadPackagesAndLog второй раз не добавляется
Удаляем автозагрузчик Autoloader::loadPackagesAndLog и добавляем его в начало стека. За это отвечает параметр $prepend=true
spl_autoload_unregister(array('Autoloader', 'loadPackagesAndLog'));
spl_autoload_register(array('Autoloader', 'loadPackagesAndLog'), $throw=false, $prepend=true);
var_dump(spl_autoload_functions());
// Output:
// ---------
array(2) {
[0]=>
array(2) {
[0]=>
string(10) "Autoloader"
[1]=>
string(18) "loadPackagesAndLog"
}
[1]=>
array(2) {
[0]=>
string(10) "Autoloader"
[1]=>
string(12) "loadPackages"
}
}
}
Удаляем все зарегистрированные автозагрузчики и убеждаемся, что стек автозагрузки пуст
spl_autoload_unregister(array('Autoloader', 'loadPackages'));
spl_autoload_unregister(array('Autoloader', 'loadPackagesAndLog'));
var_dump(spl_autoload_functions()). PHP_EOL;
// Output:
// ---------
array(0) {
}
Стек пуст, и функция
__autoload(), объявленная ранее, не восстанавливается в стеке.
Использование автоматического загрузчика с пространствами имен
Если вы — счастливчик и идете в ногу со временем, если ваши проекты уже используют PHP 5.3, и понятие пространства имен для вас не пустой звук, то вы будете избавлены от излишних телодвижений, связанных с автоматической загрузкой классов, т.к. все, что необходимо и достаточно сделать — это добавить следующий код:
spl_autoload_extensions(".php");
spl_autoload_register();
В данном случае список скриптов, в которых производится поиск классов, ограничивается лишь
*.php файлами, а затем активируется автозагрузчик по умолчанию, который отображается в стеке автозагрузки как
«spl_autoload» (предварительно необходимо добавить в
include_path путь к директории, содержащей пространство имен). После этих нехитрых манипуляций обращения вида
$classInstance = new Some\Package\Class();
будут выполнять вызов функции spl_autoload('Some\Package\Class'), которая, в свою очередь, будет пытаться загрузить класс Class.php из дерева Some/Package.
Оставшиеся вопросы
В процессе тестирования не были решены некоторые вопросы:
в документации по функции
spl_autoload_unregister() говорится, что при удалении из стека последнего зарегистрированного автозагрузчика происходит деинициализация стека. На практике не удалось добиться данного поведения.
spl_autoload_functions() возвращает пустой массив, а не
false, как ожидалось.