Автозагрузка классов в приложениях на Zend Framework

Статья была написана во времена Zend Framework 1.6.0

Статья связана с Zend Framework (далее ZF). Но так же она будет полезна, если вы используете похожие с ZF соглашения по именованию классов.

Автору всегда нравилась в PHP5 возможность автозагрузки классов, поэтому в своих приложениях обычно использует ее.

Автозагрузка классов ZF

Давайте посмотрим, что ZF предлагает по данному поводу.

Сначала заглянем в стандарты кодирования, B.3. Соглашения по именованию:

Zend Framework использует схему именования классов, в соответствии с которой имена классов напрямую указывают на директории, где они находятся. Корневой директорией Zend Framework'а является директория "Zend/", в которой иерархически расположены все классы.

Имена классов могут содержать только буквенно-числовые символы. Числа допустимы в именах классов, но не приветствуются. Символы нижнего подчеркивания допустимы в местах разделителей пути - имя файла "Zend/Db/Table.php" должно указывать на класс с именем "Zend_Db_Table".

Автору нравится это соглашение и он старается использовать его в своих приложениях (само соглашение не ново :). Приходится привыкать к длинным названиям классов. Используя такое соглашение проще искать классы и следить за их пространством имен (уникальности имени класса).

Теперь взглянем, что предлагает ZF по загрузке файлов Глава 26. Zend_Loader:

Класс Zend_Loader включает в себя методы, помогающие производить динамическую загрузку файлов.

26.1.2. Загрузка классов:

Zend_Loader::loadClass('Container_Tree');

Строка, задающая класс, преобразуется в относительный путь посредством замены знаков подчеркивания разделителями директорий и добавления расширения '.php' в конец. В примере выше 'Container_Tree' преобразуется в 'Container/Tree.php'.

26.1.4. Использование автозагрузчика:

После регистрации метода обратного вызова автозагрузки вы можете ссылаться на классы из Zend Framework без их явной загрузки. Метод autoload() автоматически запускает метод Zend_Loader::loadClass(), когда вы ссылаетесь на класс.

При этом сам фреймворк должен лежать в одной из папок указанных в include_path.

Разработчики ZF оставляют право выбора, использовать автозагрузчик классов или нет, поэтому в самой библиотеке используется явно require_once. Если с автозагрузкой классов из ZF все, в принципе, понятно, включаем описанный автозагрузчик и добавляем путь к ZF в include_path, то со своими классами дело обстоит немного интереснее.

Структура проекта на ZF и автозагрузка собственных классов

Что нам предлагают?
8.11. Использование определенной соглашением модульной структуры директорий:

Определенная соглашением модульная структура директорий позволяет разделять различные приложения MVC в автономные единицы и повторно использовать их с различными фронт-контроллерами.

В общем модули - это хорошо, распыляться на эту тему не стоит :).
Выделим кусок структуры, что касается модуля:

core/
    controllers/
        IndexController.php
        FooController.php
    models/
        SubBar
            Foo.php
        Bar.php
    views/
        scripts/
            index/
            foo/
        helpers/
        filters/

Вот и подвох, а касается он того, что в модуле появилось много папок, где будут находиться наши классы:

  • core/controllers
  • core/models
  • core/views/helpers

Насчет контроллеров позаботился ZF, следуем рекомендациям:

$front->setControllerDirectory(array(
    'default' => '/path/to/application/controllers',
    'blog'    => '/path/to/application/blog/controllers'
));

или

/**
* Предполагается следующая структура директорий:
* application/
*     modules/
*         default/
*             controllers/
*         foo/
*             controllers/
*         bar/
*             controllers/
*/
$front->addModuleDirectory('/path/to/application/modules');

Насчет "вьюверных хелперов" ZF тоже позаботился, добавив плагин-загрузчик 26.2. Loading Plugins

$loader = new Zend_Loader_PluginLoader(array(
    'Zend_View_Helper' => 'Zend/View/Helper/',
    'Foo_View_Helper'  => 'application/modules/foo/views/helpers',
    'Bar_View_Helper'  => 'application/modules/bar/views/helpers'
));

То, что приходится работать с разными загрузчиками классов, это автору не понравилось.

Модели остаются на нашей совести. Хочется, чтоб классы моделей Core_Bar и Core_SubBar_Foo находились в core/models/Bar.php и core/models/SubBar/Foo.php сответсвенно, но тут автолоадер из ZF ничего поделать не может. Вот тут и началось раздолье. Что же делать с моделями, как сделать так, чтобы автозагрузчик в приложении был одним единственным, а еще лучше чтоб загружал он все по одному принципу? Автор сделал много проб, например, был такой вариант:

ModuleName/
    _configs/
    _controllers/
    _views/
    MainClass/
        SubClassName.php
    ClassName.php
    ...

Автолоадер остается из ZF, в include_path добавляется директория с модулями. Но эта структура не понравилась, может подчеркиванием, может заглавной буквой имени модуля(в этом случае приходилось переопределять лишние вещи в фронт-контроллере из ZF), а может еще чем…

Из вышеупомянутого плагина-загрузчика понравилась идея: определенному префиксу соответствует определенная директория. А почему бы эту идею не использовать для приложения в целом?

Итого:

Zend -> 'path/to/library/Zend'
Core -> 'path/to/module/core/models'
Core -> 'path/to/module/core/controllers'
...

Тогда если класс Zend_Controller_Front, то наш автозагрузчик найдет префикс Zend, дальше от класса останется Controller_Front, который преобразуется в Controller/Front.php(по принципу Zend_Loader), аналогично и для наших моделей: по Core_SubBar_Foo находит префикс Core, далее остается SubBar_Foo, который преобразуется SubBar/Foo.php. В результате получили отличный механизм для автозагрузки как классов из ZF, так и классов из модулей. Сам механизм очень простой, но достаточно гибкий, те же вьювер-хелперы и контроллеры хорошо вписываются в эту структуру и не только.

Остается подумать, как сделать так, чтоб не регистрировать все существующие префиксы. Не забываем про упомянутое выше соглашение по именованию классов из стандартов кодирования ZF. Тогда задача очень легко решается: регистрировать нужно папки с наборами классов, а потом эти папки просматривать и в соответствии с содержимым регистрировать префиксы. Папок с классами в хорошо продуманной файловой структуре проекта не так уж много, или описываются довольно простыми правилами.
Например: регистрируем папку с библиотеками path/to/library, по содержимому ее найдется папка Zend и остальные библиотеки.

Подумаем о модулях. Модули могут постоянно добавляться, но разница добавленных папок с классами будет в названии самого модуля, если предположить, что у модулей одинаковая файловая структура (а оно так и должно быть, по идее). Приходит на ум добавить папку с модулями, а при нахождении модуля добавлять необходимый суффикс.

Например, из вышеприведенной структуры модуля:

Loader->addDir('path/to/modules', 'suffix/to/models');
Loader->addDir('path/to/modules', 'suffix/to/views/helpers');
...

После того как идея есть, дело остается за малым - реализовать :)

Пример загрузчика

P.S. Автор знаком с ZF начиная с 0.1.x версий. Первое знакомство: PHPIns!de #18, Июль'2006.