Продолжаем серию статей о фишках и возможностях php. На этот раз расскажу о возможности автоматической загрузки классов
В предыдущей статье я упоминал как раз о том, что буду писать про возможности автоматической загрузки. А догадливый Дима спросил, буду я писать про __autoload()
или про spl_autoload_register()
. Сначала я, было, ответил, что конечно же про spl_
, ведь __autoload()
признан устаревшим. Ан нет - не получится, придется рассказать про оба, чтобы у вас сформировалась полная и четкая картина про автоматическую загрузку.
Среди разработчиков сформировалось определенное мнение, что хранить свои классы нужно с соблюдением определенной структуры, централизованно, с правилами именования. Но при этом есть одна неприятная необходимость - явно подключать в своем коде каждый такой файл. Если файлов 1-2, то конечно это не проблема. Но когда файлов десятки и сотни - то это становится проблемой
В php5 эта проблема была решена с помощью специальной функции __autoload()
Как работает __autoload()
?
Когда в коде вы используете какой-то класс, но этот класс не подключен к проекту, то перед тем, как php сгенерирует FatalError
, будет вызвана эта функция. На вход ей будет передано имя класса. В данной функции должен содержаться код, который попытается подключить файл с этим классом, Если уж внутри функции класс не будет подключен, тогда вылезет FatalError
.
В общем-то при соблюдении определенной структуры хранения файлов, можно легко по имени файла найти файл с нужным классом в определенной директории и подключить файл с классом, тогда ошибки не будет.
Как я храню классы в битриксе
Основной директорией для хранения классов у меня является /local/classes
.
Если класс не содержится в пространстве имен, то он лежит прям тут. Имя файла соответствует имени класса, имя класса должно начинаться с заглавной буквы, а слово должно быть написано в CamelCase нотации (например GoogleAdwords, ZhurovMe и т.д, т.е. каждое новое слово в словосочетании начинается с большой буквы, между словами нет пробелов). В каждом файле описан только один класс. Если класс использует пространство имен, то нужно транслировать пространство имен на директории внутри /local/classes
Например, есть класс My\Super\Puper\Duper\Class
.
Для такого класса создаем файл по адресу:/local/classes/My/Super/Puper/Duper/Class.php
Наименование корневого простраства имет, как правило, соответствует имени автора этого класса (читай - вендора). В моем случае пускай это будет Zhurov. Или если вы работаете в компании с названием Boogle, то именно с этого пространства имен имеет смысл назвать свой класс. Тогда получится /local/classes/Boogle/Super/Puper/Duper/Class.php
Соблюдение подобной структуры позволяет использовать функцию __autoload()
примерно следующего содержания:
function __autoload($className)
{
$dr = $_SERVER['DOCUMENT_ROOT'];
$ds = DIRECTORY_SEPARATOR;
$className = ltrim($className, '\\');
$classDir = $dr . '/local/classes';
$namespace = '';
if ($lastNsPos = strripos($className, '\\'))
{
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', $ds, $namespace) . $ds . $className;
}
$fileName = $classDir . $ds . $fileName . '.php';
if (file_exists($fileName)) require $fileName;
}
Т.е. по коду мы видим, что функция принимает на вход параметр $className
. Определяем, содержит ли имя класса пространства имен (по символу ‘'). Если есть - заменяем их на ‘/’ и ищем файл с названием {$className}.php
в указанной директории. Если же неймспейса нет, то просто пытаемся подключить файл из этой директории.
Все так просто, что просто ужас. Это работает, и вам не нужно писать на каждый свой класс отдельный include/require
spl_autoload_register()
Ну в __autoload
есть одна, и очень значительная проблема. Ее можно определить только в единственном экземпляре! Т.е. все библиотеки сторонних разработчиков, которые вы хотите использовать в своем проекте, придется либо перестраивать под ваш autoload, либо не использовать для них autoload, а это опять гора инклюдов …
Еще круче будет, если внутри библиотеки разработчик сам определяет свою функцию __autoload()
и тогда у вашего проекта будет с библиотекой конфликт.
Поэтому в php5.1 выходит специальная функция - spl_autoload_register()
. Можете теперь написать сколь угодно много функций с разными именами, и зарегистрировать каждую из них в качестве автолоадера! Круто? Еще бы!
Адаптируя предыдущий пример - получим следующее:
function myCustomAutoloader($className)
{
$dr = $_SERVER['DOCUMENT_ROOT'];
$ds = DIRECTORY_SEPARATOR;
$className = ltrim($className, '\\');
$classDir = $dr . '/local/classes';
$namespace = '';
if ($lastNsPos = strripos($className, '\\'))
{
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', $ds, $namespace) . $ds . $className;
}
$fileName = $classDir . $ds . $fileName . '.php';
if (file_exists($fileName)) require $fileName;
}
spl_autoload_register('myCustomAutoloader');
Определяем функцию с любым именем, и регистрируем ее в качестве автолоадера!
Да - есть один нюанс - если вы до этого использовали функцию с именем __autoload()
, то при использовании функции spl_autoload_register()
вся «магичность» __autoload()
пропадает, и если вы хотите использовать старый __autoload()
наряду с другими функциями такого же назначения, то придется и ее зарегистрировать отдельно. Дорабатываем пример:
function myCustomAutoloader($className)
{
$dr = $_SERVER['DOCUMENT_ROOT'];
$ds = DIRECTORY_SEPARATOR;
$className = ltrim($className, '\\');
$classDir = $dr . '/local/classes';
$namespace = '';
if ($lastNsPos = strripos($className, '\\'))
{
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', $ds, $namespace) . $ds . $className;
}
$fileName = $classDir . $ds . $fileName . '.php';
if (file_exists($fileName)) require $fileName;
}
spl_autoload_register('myCustomAutoloader');
if (function_exists('__autoload'))
spl_autoload_register('__autoload');
Есть еще несколько функций с приставкой spl_autoload*
, но их изучение предлагаю сделать вам в качетсве домашки. Особое внимание стоит обратить на spl_autoload
- эта функция уже является реализацией автолоадера по умолчанию. Т.е. если у вас не определены ваши кастомные автолоадеры, то будет вызвана функция spl_autoload
, которая попытается найти и подключить файл с нужным классом. А вот как она ищет класс предлагаю выяснить самостоятельно.
PSR-0 и PSR-4
Все описанное выше - это, конечно же, очень хорошо. Но лучше ориентироваться на стандарты. Есть такая организация PHP-FIG, в которую входят разработчики самых передовых решений из мира PHP. На тему автозагрузки существует 2 стандарта - PSR-0 (в данный момент является устаревшим) и PSR-4. Эти стандарты определяют собой набор правил, которым должен удовлетворять автозагрузчик для PHP проекта. Не стоит использовать код, описанный выше, как панацею. Напишите свою (или возьмите готовую) имплементацию стандарта, и используйте в своих задачах, в блог вставлять примеры кода не вижу смысла.
Спасибо за внимание!