В данной статье я хочу рассказать как обстоит дело с автозагрузкой классов в PHP, а также о распространенных подходах при реализации автозагрузки.
Старый добрый PHP
В старом PHP коде обычной практикой было использование функций require, require_once, include, include_once для подгрузки файлов, содержащих необходимые классы, к примеру:
<?php require_once 'src/User.php'; $user = new User('Victor');
таких require_once в начале файлов скапливалось огромное количество.
Плохо ли это? Да.
Почему? Потому что:
- файлы подгружались в любом случае, даже тогда когда в этом не было никакой необходимости
- постоянно приходилось писать require/include и можно было запросто забыть подключить какой-либо файл
- в случае перемещения какого-либо файла, приходилось менять все относящиеся к нему require
Изменения в PHP 5
Те времена давно прошли, т.к. в PHP 5 появился механизм автозагрузки классов и функция __autoload(), которая вызывается каждый раз, когда создается объект неизвестного класса. Единственное что оставалось разработчику, так это реализовать ее, и больше не было необходимости писать require и т.д:
<?php function __autoload($class) { require_once "src/$class.php"; } // далее можно просто создавать объекты $user = new User('Victor');
т.к. ранее нигде не был подключен файл, содержащий класс User, то будет вызвана функция __autoload(), которой будет передано имя класса как параметр $class, и она в свою очередь попробует подключить файл содержащий данный класс.
С версии PHP 5.1.2 доступна новая функция spl_autoload_register()
bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )
Данная функция позволяет регистрировать любую переданную ей функцию как реализацию механизма автозагрузки классов. Если же вызвать ее без параметров, то в качестве реализации автозагрузки будет выступать функция spl_autoload()
Этот механизм имеет свою очередь, поэтому можно регистрировать более одной функции для разрешения вопросов автозагрузки классов. Все функции будут вызваны в порядке их регистрации. Также с версии 5.3 добавлена поддержка неймспейсов.
Что мы имеем?
+ удобный механизм для автозагрузки классов с уже готовой реализацией по умолчанию
+ файлы загружаются лишь тогда, когда это необходимо
+ нет необходимости писать кучу require
Варианты реализации автозагрузки классов
Предлагаю ознакомиться с распространенными подходами в реализации автозагрузчиков и написать свои.
Ручная регистрация классов для автозагрузки
Данный способ предполагает создание одного автозагрузчика с возможностью регистрации классов в нем. Это может происходить следующим образом:
<?php // src/Autoloader/MapAutoloader.php /** * Наш автозагрузчик классов */ class MapAutoloader { // карта соответствий названий классов и файлов где они хранятся protected $classesMap = array(); public function registerClass($className, $absolutePath) { if (file_exists($absolutePath)) { $this->classesMap[$className] = $absolutePath; return true; } return false; } public function autoload($class) { if (!empty($this->classesMap[$class])) { require_once $this->classesMap[$class]; return true; } return false; } }
далее необходимо зарегистрировать наш автозагрузчик:
<?php // examples/map.php $srcDir = __DIR__ . '/../src'; require_once $srcDir . '/Autoloader/MapAutoloader.php'; $autoloader = new MapAutoloader(); // регистрируем наш автозагрузчик spl_autoload_register(array($autoloader, 'autoload'));
Приступим к его непосредственному использованию. Например у нас есть два класса User и Task, которые находятся соответственно в /src/Model/User.php и /src/Model/Task.php, тогда чтобы включить их автозагрузку, нам необходимо сделать следующее:
<?php // examples/map.php $autoloader->registerClass('User', $srcDir . '/Model/User.php'); $autoloader->registerClass('Task', $srcDir . '/Model/Task.php');
потом в любом месте, где нам понадобится создать объекты этих классов, достаточно будет просто написать:
<?php // examples/map.php $user = new User('Victor', 'Melnik'); $task = new Task('Write about autoloaders in PHP'); echo $user . ' task is: "' . $task . '"'; // результат: // Melnik Victor task is: "Write about autoloaders in PHP"
и сработает метод autoload нашего автозагрузчика.
Данный способ не идеален и имеет свои достоинства:
+ простота реализации
+ вместо постоянных require достаточно один раз зарегистрировать класс в автозагрузчике
+ файлы подгружаются только по мере их необходимости
+ в случае изменения местоположения файла, достаточно изменить его путь в одном месте
+ позволяет добавлять сторонние библиотеки в проект, для этого нужно лишь добавить их классы в карту автолоадера
и недостатки:
- необходимо регистрировать классы вручную
- не позволяет по имени класса быстро определить где он находится
Имена классов как указатели пути к файлу
Данный способ автозагрузки классов используется в шаблонизаторе Twig, а также использовался в Zend Framework'e первой версии. Суть его в том, что само имя класса указывает на папку, где находится файл, содержащий этот класс. Это легко понять на примере:
<?php // src/Model/Article.php class Model_Article { protected $title; protected $content; public function __construct($title) { $this->title = $title; } // геттеры и сеттеры... }
На основе данного примера легко написать функцию для автозагрузки классов, следующих подобному соглашению:
<?php // src/Autoload/underscore.php function __autoload($class) { $baseDir = __DIR__ . '/../'; $path = $baseDir . str_replace('_', '/', $class) . '.php'; if (file_exists($path)) { require_once($path); return true; } return false; }
Вся его работа сводится к одному действию - замене подчеркиваний в названии класса на прямой слеш, формируя таким образом путь к файлу. В данном примере жестко указана корневая точка отсчета начала пути в файловой системе, улучшение этого момента я оставляю на совести читателя.
Пример использования:
<?php // examples/underscore.php // загружаем функцию для автолоада классов require_once __DIR__ . '/../src/Autoloader/underscore.php'; // использование $article = new Model_Article('Autoloaders in PHP'); $article->setContent('Some very interesting stuff here...'); echo $article->getTitle();
Рассмотрим преимущества и недостатки данного автозагрузчика.
Плюсы:
+ нет необходимости вручную регистрировать классы
+ файлы подключаются только по мере необходимости
+ по имени файла легко определить где он находится
+ исключается возможность конфликтов в именах классов, т.к. не может быть два файла с одинаковым именем в файловой системе
Минусы:
- нет поддержки автозагрузки классов, не следующих данному соглашению при именовании классов, например нет поддержки неймспейсов
- в случае серьезной реорганизации структуры папок придется везде переписывать имена классов
Автозагрузка классов, использующих неймспейсы
С момента появления неймспейсов в PHP данный способ стал одним из самых популярных. Суть в том, что неймспейс необходимо связать с определенной папкой в файловой системе, а все вложенные папки/файлы будут подхватываться автоматически. Ниже приведу пример упрощенного автозагрузчика, поддерживающего работу с неймспейсами:
<?php // src/Autoloader/NamespaceAutoloader.php class NamespaceAutoloader { // карта для соответствия неймспейса пути в файловой системе protected $namespacesMap = array(); public function addNamespace($namespace, $rootDir) { if (is_dir($rootDir)) { $this->namespacesMap[$namespace] = $rootDir; return true; } return false; } public function register() { spl_autoload_register(array($this, 'autoload')); } protected function autoload($class) { $pathParts = explode('\\', $class); if (is_array($pathParts)) { $namespace = array_shift($pathParts); if (!empty($this->namespacesMap[$namespace])) { $filePath = $this->namespacesMap[$namespace] . '/' . implode('/', $pathParts) . '.php'; require_once $filePath; return true; } } return false; } }
Далее рассмотрим на примере использование этого автозагрузчика:
<?php // examples/namespace.php require_once __DIR__ . '/../src/Autoloader/NamespaceAutoloader.php'; // задаем соответствие для неймспейса Model и регистрируем автозагрузчик $autoloader = new NamespaceAutoloader(); $autoloader->addNamespace('Model', __DIR__ . '/../src/Model'); $autoloader->register(); // способ также поддерживает алиасы use Model as Alias; $robot = new Alias\Robot('Bender'); $robot->extendVocabulary("Lets face it, comedy's a dead art form. Now tragedy! Ha ha ha, that's funny."); $robot->extendVocabulary("Congratulations, Fry! You snagged the perfect girlfriend. Amy's rich, she probably has got other characteristics..."); $robot->extendVocabulary("I'm a real toughie!"); echo $robot->getName() . ': ' . $robot->saySomething();
В данном примере мы добавили неймспейс Model, который указывает на папку src/Model. Далее когда мы создаем объект класса Alias\Robot, автозагрузчик получает строку Model\Robot. По части Model формируется путь src/Model, а по Robot - Robot.php, итоговый путь к файлу с классом Robot получается src/Model/Robot.php.
Каковы же плюсы и минусы такого подхода?
Плюсы:
+ поддержка неймспейсов, которые используются во многих современных библиотеках
+ упрощенная регистрация неймспейсов по сравнению с регистрацией классов в первом автозагрузчике
+ отсутствие конфликтов в именах классов, т.к. каждый файл живет в своем неймспейсе
Минусы:
- необходимо регистрировать неймспейсы
В итоге мы получили три, пусть и упрощенных но рабочих, автозагрузчика, каждый из которых поддерживает загрузку определенных классов. Данная статья вышла относительно длинной, и надеюсь никто не заснул пока ее читал, а также вынес что-то полезное для себя. Последние два автозагрузчика работали только с каким-то конкретным типом классов (подчеркивания или нейсмпейсы), поэтому в следующий раз я опишу как устроен автозагрузчик в Composer'e, т.к. он позволяет загружать любые классы.
P.s. Исходный код автозагрузчиков из статьи, а также примеры их использования можно увидеть здесь.