Ниже опубликован перевод статьи Matthew Weier O’Phinney Developing a Comprehensive Autoloader.
В этой статье я буду рассуждать о развитии и нововведениях Zend_Loader_Autoloader и смежного функционала. Тем не менее, основная цель статьи — показать различные проектировочные решения, которые ведут к созданию всеобъемлющего автозагрузчика для ваших PHP-приложений. Автозагрузка, внешне тривиальная задача, имеет много тонкостей, которые часто упускают.
История
Начиная с релиза Zend Framework 1.0, мы имеем Zend_Loader::autoload() и Zend_Loader::registerAutoload() (который использовал функцию PHP spl_autoload_register()). В течение этого времени мы сталкивались с проблемами в их использовании.
Во-первых, мы пытались загрузить класс используя прямой вызов include. Если это не срабатывало и вы получали notice, указывающий, что include потерпел неудачу — и это раздражало некоторых пуристов, которые считают, что автозагрузчик не должен выдавать никакие ошибки, так как другие автозагрузчики в цепочке могут загрузить этот класс.
Мы пытались использовать оператор подавления (‘@’). Это избавило от вывода ошибок (но они по-прежнему записывались в логи) — но также имеет очень неприятный побочный эффект: если подключаемый класс имел ошибки уровней парсинга или компиляции, ничего не выводилось, и вы видели пустой белый экран без какой-либо информации.
Тогда мы попытались сначала проверять файл на возможность чтения. Тем не менее, это является очень дорогостоящим процессом, подключение множества файлов сильно нагружало сервер; проблем с производительностью было достаточно, чтобы вернуться к прежнему варианту.
Тогда мы пришли к нынешнему варианту: он был рядом, но он не подходил для решения всех проблем используя упрощённый подход. Таким образом родился Zend_Loader_Autoloader.
Zend_Loader_Autoloader: Задачи и Дизайн
Этот компонент предназначен для решения следующих задач:
- Обеспечение соответствия имён. Если namespace-префикса класса нет в списке зарегистрированных namespace’ов, будет просто возвращён false. Это устраняет множество проблем с самого начала.
- Разрешение автозагрузки как запасного варианта автозагрузки. В случае, если команда широко распространена, или используется неопределённый набор namespace-перфиксов, автозагрузчик следует настраивать таким образом, чтобы он искал любые префиксы.
- Разрешение оператора подавления ошибок. Мы понимаем — и PHP-сообщество тоже — подавление ошибок плохая мысль. Это является маской для реальных проблем приложения. Таким образом, по умолчанию мы отключили его. В то же время, если разработчик настаивает на включении, мы позволяем сделать это.
- Можно определить свой собственный callback для автозагрузки. Некоторые разработчики не хотят использовать Zend_Loader::loadClass() для автозагрузки, но хотят сделать это через механизмы ZF. Мы позволяем использовать альтернативный callback для автозагрузки.
- Можно управлять цепочкой вызовов spl_autoload. Назначение этого в возможности использования дополнительных автолоадеров, например, загрузчиков ресурсов для классов, которые могут быть зарегистрированы до или после основного ZF-автозагрузчика.
Последний пункт оказался довольно интересным. Как часть стратегии автозагрузки ZF, мы разработали Zend_Loader_Autoloader_Resource и его брата Zend_Application_Module_Autoloader; они позволяют вам сопоставить namespace-префиксы и компоненты с каталогами и подкаталогами — которые позволяют вам использовать деревья наподобие следующего:
1 2 3 4 5 6 7 8 | . |-- forms | `-- Guestbook.php // Foo_Form_Guestbook |-- models | |-- DbTable | | `-- Guestbook.php // Foo_Model_DbTable_Guestbook | |-- Guestbook.php // Foo_Model_Guestbook | `-- GuestbookMapper.php // Foo_Model_GuestbookMapper |
Экземпляры этих автозагрузчиков имеют метод autoload(), который мы пытались зарегистрировать с spl_autoload(). Проблема заключается в том, что, если мы хотите добавить автозагрузчик в начало стека, или вставить между своим и автозагрузчиком по умолчанию, он не будет работать. По какой-то причине, возвращаемое значение spl_autoload_functions() не содержит некоторые callback’и, переданные туда. Единственным, что может быть повторно зарегистрировано, были callback’и с вызовом статичных методов — которые мы не хотим использовать, чтобы мы могли иметь несколько автозагрузчиков ресурсов для разных областей наших проектов.
Чтобы решить эту проблему, мы разработали реестр с Zend_Loader_Autoloader для поддержки этих callback’ов автозагрузчика. Как выяснилось, это было отличное решение, так как это позволило нам сделать кое-что ещё: автолоадеры ориентированы на некоторый namspace-префикс, что позволяет нам запускать только те автолоадеры, которые используют нужный нам префикс.
Zend_Loader_Autoloader: Миграция
В релизом Zend Framework 1.8.0 мы пометили Zend_Loader::autoload и Zend_Loader::registerAutoload() как deprecated. Простейший путь заменить их на новый подход:
1 2 3 4 5 6 7 | // Если вы имели: require_once 'Zend/Loader.php'; Zend_Loader::registerAutoload(); // в большинстве случаев, вам подойдёт код: require_once 'Zend/Loader/Autoloader.php'; Zend_Loader_Autoloader::getInstance(); |
Тем не менее, помните: Zend_Loader_Autoloader основан на пространствах имён. По умолчанию, зарегистрированы только Zend_ и ZendX_. Если вы имеете дополнительные пространства имён, вы должны сделать одно из нижеперечисленного:
- Предпочтительно: Регистрируйте каждый namespace в автозагрузчике. Вы можете сделать это используя метод registerNamespace(), который принимает один префикс namespace или массив из них. Префикс namespace должен включать замыкающий символ подчёркивания, чтобы уменьшить возможность коллизий
1
2
3$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('Foo_');
$autoloader->registerNamespace(array('Foo_', 'Bar_')); - Альтернатива: если вы не знаете, какой namespace используется, или вы используете библиотеки компонентов, например, PEAR, который не имеет общего префикса пространства имён, вы можете перевести автозагрузчик в fallback-режим:
1
2$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->setFallbackAutoloader(true);
Если вы продвинутый пользователь, можете также начать использовать Zend_Application. Zend_Application принимает опцию конфигурации “autoloaderNamespaces”, которая может быть массивом используемых пространств имён.
Загрузка ресурсов
Я уже упоминал автозагрузку ресурсов ранее. Zend_Loader_Autoloader_Resource определяет API, через который вы указываете namespace-префикс классов и базовый путь к файлам с этим префиксом. Затем вы можете зарегистрировать “ресурсы”, которые будут привязаны к определённому префиксу и располагаться в подкаталоге базового пути префикса. Это особенно полезно для группировки кода приложения в случае, если вы не хотите жёсткой иерархии, а вместо этого хотите сосредоточиться на группировке по обязанностям.
Как они связаны с Zend_Loader_Autoloader? Довольно просто: конструктор Zend_Loader_Autoloader_Resource регистрирует экземпляр Zend_Loader_Autoloader, использующий предоставляемый namespace-перфикс. Это позволяет основному экземпляру автозагрузчика перехватывать классы с этим namespace-префиксом и передать их в заргузчик ресурсов для изменения.
Как пользоваться автозагрузчиками ресурсов на практике? Простейший путь к пониманию, как они работают, взглянуть изнутри на Zend_Application_Module_Autoloader, который расширяет автозагрузчик ресурсов и добавляет рекоммендуемые префиксы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | class Zend_Application_Module_Autoloader extends Zend_Loader_Autoloader_Resource { public function __construct($options) { parent::__construct($options); $this->initDefaultResourceTypes(); } public function initDefaultResourceTypes() { $basePath = $this->getBasePath(); $this->addResourceTypes(array( 'dbtable' => array( 'namespace' => 'Model_DbTable', 'path' => 'models/DbTable', ), 'form' => array( 'namespace' => 'Form', 'path' => 'forms', ), 'model' => array( 'namespace' => 'Model', 'path' => 'models', ), 'plugin' => array( 'namespace' => 'Plugin', 'path' => 'plugins', ), 'service' => array( 'namespace' => 'Service', 'path' => 'services', ), 'viewhelper' => array( 'namespace' => 'View_Helper', 'path' => 'views/helpers', ), 'viewfilter' => array( 'namespace' => 'View_Filter', 'path' => 'views/filters', ), )); $this->setDefaultResourceType('model'); } } |
Ресурс — это имя, соответствующее пространству имён компонента (которое добавлено в пространство имён автозагрузчика) и путь (относительно базового пути автозагрузчика). На практике это выглядит примерно так:
1 2 3 4 | $loader = new Zend_Application_Module_Autoloader(array( 'namespace' => 'Blog', 'basePath' => APPLICATION_PATH . '/modules/blog', )); |
Теперь можно просто инстанцировать соответствующие классы:
1 2 | // класс расположен в файле APPLICATION_PATH . '/modules/blog/models/Comments.php': $comments = new Blog_Model_Comments(); |
Замечание: если вы используете ресурс для бутстраппинга модулей, и определяете бутстраппинг в ваших модулях, которые используют Zend_Application_Module_Bootstrap, модуль автозагрузки ресурсов будет настроен автоматически.
Это устраняет необходимость во множестве выражений require_once, или настройке include_path, что также поможет вам отвязать ваш код от файловой системы и структура проекта будет более гибкой.
Зачем загружать всё автоматически?
Итак, зачем нам загружать всё автоматически?
В прошлом, когда существовал ряд проблем с автозагрузкой, и она работала медленнее, чем простое использование require_once. Тем не менее, начиная с серии релизов PHP 5.2, эта ситуация резко изменилась с добавлением realpath-кэша. Кроме того, в крупных фреймворках как Zend_Framework, зачастую есть ряд классов, которые являются зависимыми — и многочисленные require_once в начале каждого класса могут привести к значительным замедлениям, особенно в системах, где I/O является дорогостоящим.
Короче говоря, использовать автозагрузчик надо потому, что он откладывает загрузку файлов до последнего момента и гарантирует, что он будет загружен только один раз, это может быть серьёзным повышением производительности — особенно, если вы потратите время на чистку кода от вызовов require_once, прежде чем перейдёте к развёртыванию.
Другая причина, о которой я намекнул в последнем разделе: используя автозагрузку, у вас нет необходимости беспокоиться о том, где класс существует в вашем проекте. Во многих основанных на ZF проектах, с которыми я работал или ознакомлялся, имели много комплексных, сложных систем, используемых в попытке сопоставить классы с файлами, таких как изменение include_path (и помните, чем больше будет каталогов в include_path, тем сильнее это ухудшит производительность), использование Zend_Controller_Front для поиска каталогов модулей с целью создать относительный путь поиска, или подстановок в стиле dirname(__FILE__). Используя автозагрузчик ресурсов, вы можете выбрать, как вы хотите организовать подмножество вашего кода, написать правила и зарегистрировать их — и эти классы будут доступны отовсюду в вашем приложении.
Выводы
Автозагрузка кажется, и часто таки есть, простой, прямолинейной задачей. Тем не менее, создание всеобъемлющего автозагрузчика может быть сложной задачей. Вам необходимо беспокоиться обо всём, от приятных игр с другими автозагрузчиками до уверенности, что вы получаете актуальные сообщения об ошибках;от возможности конфигурирования автозагрузчиков в стеке до понимания ограничений и принуждений автозагрузчика SPL; от сопоставления стилей классов PEAR и ZF до маппинга классов приложения.
Надеюсь, теперь у вас сложилось лучше понимание того, как работают автозагрузчики, и некоторые проектировочные решения, которые следует учитывать при создании своего автозагрузчика для PHP.тем не менее, вы можете использовать решение, предложенное в этой заметке: Zend_Loader_Autoloader.
Александр Махомет says:
Сергей, авторазгрузчик это сильно
10 мая 2009, 22:27Спасибо за перевод.
Александр Махомет says:
Allow the autoloader to act as a fallback autoloader. – Разрешение автозагрузки как запасного варианта авторазгрузки.
Плохо получилось.
10 мая 2009, 22:30Сергей Митрошин says:
> авторазгрузчик это сильно
Пальцы заплетаются
Спасибо за замечание
> Плохо получилось.
10 мая 2009, 23:24Буду благодарен за поправки.
Юрий Истомин says:
Так я не понял – они предлагают всем-всем моим классам подобавлять префиксы? Моделям Model_Foo, плагинам Plugin_Foo, если это все лежит в модулях, то Modulename_Model_Foo и т.д.?
15 мая 2009, 12:41Сергей Митрошин says:
В статье подразумевается, что разработчик уже придерживается этого стиля именования.
15 мая 2009, 13:12На самом деле, это довольно удобно.
Юрий Истомин says:
А если у меня у классов префиксов нет (ну или есть, но суффиксы – FooModel), то мне Zend_Loader_Autoloader не поможет?
15 мая 2009, 13:33Сергей Митрошин says:
Думаю, можно его доработать под такой функционал. Но сам я пока не вникал.
15 мая 2009, 18:03Олег says:
Большое спасибо! Очень помогла статья!
24 июля 2010, 16:09