До версии PHP 5 обычной практикой было использование функций require, require_once, include, include_once для загрузки файлов, содержащих нужные классы.
Пример использования функций:
1 2 3 4 5 |
<?php requre_once 'lib/src/SomeClass.php'; $someObject = new SomeClass('Object'); |
Проблема такого подхода в том, что:
- Необходимо указывать каждый файл в котором содержится необходимый класс.
- Использование обращения к объекту не подгруженного класса вызывало ошибку.
- Плюс, указанные файлы подгружались всегда, независимо от их необходимости.
- Изменение структуры хранения файлов, влекло за собой переписывание многочисленных включений.
- Легко было запутаться в том, что необходимо включить в файл, а что включать не нужно.
Как итог, огромное количество включений ненужных файлов и ошибки в зависимостях из-за отсутствия включения нужных.
Новый подход в PHP 5
Новый механизм, предложенный в PHP5 изменил подход, предложив автозагрузку классов. За автозагрузку отвечала aункция __autoload() которая вызывалась каждый раз, когда создавался объект неизвестного класса.
Реализовав функцию автозагрузки классов мы получали простой механизм управления включением классов.
1 2 3 4 5 6 7 8 |
<?php function __autoload($class){ require_once "lib/src/SomeClass.php"; } $someObject = new SomeClass("Object"); |
Класс SomeClass ранее не был подключен и будет вызвана функция __autoload(), которой будет передано название класса как параметр $class.
В свою очередь, она попробует подключить файл содержащий данный класс.
Улучшенный автозагрузчик
Подход с автозагрузкой классов был настолько успешен, что уже в версии PHP 5.1.2 стала доступна новая функция: spl_autoload_register();
1 |
spl_autoload_registr (callable $autoload_function = ? , bool $throw = true, bool $prepend = false) : bool |
Список параметров
autoload_function
Имя функции, реализующей метод spl_autoload(). Если аргумент не задан, будет зарегистрирована реализация по умолчанию.
throw
Этот параметр определяет, должна ли spl_autoload_register() выбрасывать исключение, если зарегистрировать autoload_function
оказалось невозможным.
prepend
Если передано значение true, spl_autoload_register() поместит указанную функцию в начало очереди вместо добавления в конец.
Данная функция позволяет регистрировать любую переданную ей функцию как реализацию механизма автозагрузки классов. Если вызвать ее без параметров, то в качестве механизма автозагрузки классов будет выступать функция spl_autoload().
Стоит помнить, что функция spl_autoload_register() имеет свою очередь, поэтому можно регистрировать более одной функции. Функции обратного вызова будут вызываться в порядке их регистрации.
В версии PHP5.3 добавлена поддержка пространства имен (namespase).
Распространенные подходы автозагрузки классов
Каждый из подходов заслуживает внимания. По распространенности, первый (ручной подход) уступает двум другим примерам. В то же время, автозагрузка с использованием пространства имен, на мой субъективный взгляд, используется куда чаще имен файлов как указателей.
Регистрация автозагрузки классов вручную
Все просто, создаем автозагрузчик и регистрируем в нем все классы вручную.
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 |
<?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; } } |
Далее, необходимо зарегистрировать автозагрузчик:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?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, тогда чтобы включить их автозагрузку, нам необходимо сделать следующее:
1 2 3 4 5 6 |
<?php // examples/map.php $autoloader->registerClass('User', $srcDir . '/Model/User.php'); $autoloader->registerClass('Task', $srcDir . '/Model/Task.php'); |
потом в любом месте, где нам понадобится создать объекты этих классов, достаточно будет просто написать:
1 2 3 4 5 6 7 8 9 10 11 |
<?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 Fraemwork первой версии. Как ясно из заголовка, само имя класса указывает на папку, где находится файл, содержащий этот класс.
Простейший пример именования, через нижнее подчеркивание, которое в последствии обрабатывается функцией str_replace('_', '/', '$class')
.
Все просто, получаем имя класса Model_SomeClass и преобразуем его в путь «Model/SomeClass«.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php // src/Model/Article.php class Model_Article { protected $title; protected $content; public function __construct($title) { $this->title = $title; } // геттеры и сеттеры... } |
На основе данного примера легко написать функцию для автозагрузки классов, следующих подобному соглашению:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?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; } |
Вся его работа сводится к одному действию — замене подчеркиваний в названии класса на прямой слеш, формируя таким образом путь к файлу. В данном примере жестко указана корневая точка отсчета начала пути в файловой системе, улучшение этого момента я оставляю на совести читателя.
Пример использования:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?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(); |
Рассмотрим преимущества и недостатки данного автозагрузчика.
Плюсы:
+ нет необходимости вручную регистрировать классы
+ файлы подключаются только по мере необходимости
+ по имени файла легко определить где он находится
+ исключается возможность конфликтов в именах классов, т.к. не может быть два файла с одинаковым именем в файловой системе
Минусы:
— нет поддержки автозагрузки классов, не следующих данному соглашению при именовании классов, например нет поддержки неймспейсов
— в случае серьезной реорганизации структуры папок придется везде переписывать имена классов
Автозагрузка классов использующих пространства имен (namespace)
На сегодняшний день это самый популярный подход и лучший способ применения самих пространств имен. Суть его в том, что мы регистрируем пространство имен: папку в файловой системе, а все вложенные файлы и папки будут подхватываться автоматически. В сочетании с автозагрузчиком классов этот подход позволил экономить не только ресурсы серверов, но и время программистов.
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 |
<?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; } } |
Далее рассмотрим на примере использование этого автозагрузчика:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?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. Исходный код автозагрузчиков из статьи, а также примеры их использования можно увидеть здесь.