Продолжаем серию статей о фишках и возможностях 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 проекта. Не стоит использовать код, описанный выше, как панацею. Напишите свою (или возьмите готовую) имплементацию стандарта, и используйте в своих задачах, в блог вставлять примеры кода не вижу смысла.

Спасибо за внимание!