PHP

Автозагрузка классов в PHP с использованием SPL

Как это ни удивительно, но на вопрос об автоматическом загрузчике классов 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, как ожидалось.
Теги:
автозагрузка, класс, spl, autoload, spl_autoload_functions, spl_autoload_unregister, spl_autoload_call, spl_autoload_extensions
Добавлено: 15 Сентября 2013 01:37:31 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...