До версии PHP 5 обычной практикой было использование функций require, require_once, include, include_once для загрузки файлов, содержащих нужные классы.
Пример использования функций:
<?php
requre_once 'lib/src/SomeClass.php';
$someObject = new SomeClass('Object');
Проблема такого подхода в том, что:
- Необходимо указывать каждый файл в котором содержится необходимый класс.
- Использование обращения к объекту не подгруженного класса вызывало ошибку.
- Плюс, указанные файлы подгружались всегда, независимо от их необходимости.
- Изменение структуры хранения файлов, влекло за собой переписывание многочисленных включений.
- Легко было запутаться в том, что необходимо включить в файл, а что включать не нужно.
Как итог, огромное количество включений ненужных файлов и ошибки в зависимостях из-за отсутствия включения нужных.
Новый подход в PHP 5
Новый механизм, предложенный в PHP5 изменил подход, предложив автозагрузку классов. За автозагрузку отвечала aункция __autoload() которая вызывалась каждый раз, когда создавался объект неизвестного класса.
Реализовав функцию автозагрузки классов мы получали простой механизм управления включением классов.
<?php
function __autoload($class){
require_once "lib/src/SomeClass.php";
}
$someObject = new SomeClass("Object");
Класс SomeClass ранее не был подключен и будет вызвана функция __autoload(), которой будет передано название класса как параметр $class.
В свою очередь, она попробует подключить файл содержащий данный класс.
Улучшенный автозагрузчик
Подход с автозагрузкой классов был настолько успешен, что уже в версии PHP 5.1.2 стала доступна новая функция: spl_autoload_register();
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).
Распространенные подходы автозагрузки классов
Каждый из подходов заслуживает внимания. По распространенности, первый (ручной подход) уступает двум другим примерам. В то же время, автозагрузка с использованием пространства имен, на мой субъективный взгляд, используется куда чаще имен файлов как указателей.
Регистрация автозагрузки классов вручную
Все просто, создаем автозагрузчик и регистрируем в нем все классы вручную.
<?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 Fraemwork первой версии. Как ясно из заголовка, само имя класса указывает на папку, где находится файл, содержащий этот класс.
Простейший пример именования, через нижнее подчеркивание, которое в последствии обрабатывается функцией str_replace('_', '/', '$class').
Все просто, получаем имя класса Model_SomeClass и преобразуем его в путь «Model/SomeClass«.
<?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();
Рассмотрим преимущества и недостатки данного автозагрузчика.
Плюсы:
+ нет необходимости вручную регистрировать классы
+ файлы подключаются только по мере необходимости
+ по имени файла легко определить где он находится
+ исключается возможность конфликтов в именах классов, т.к. не может быть два файла с одинаковым именем в файловой системе
Минусы:
— нет поддержки автозагрузки классов, не следующих данному соглашению при именовании классов, например нет поддержки неймспейсов
— в случае серьезной реорганизации структуры папок придется везде переписывать имена классов
Автозагрузка классов использующих пространства имен (namespace)
На сегодняшний день это самый популярный подход и лучший способ применения самих пространств имен. Суть его в том, что мы регистрируем пространство имен: папку в файловой системе, а все вложенные файлы и папки будут подхватываться автоматически. В сочетании с автозагрузчиком классов этот подход позволил экономить не только ресурсы серверов, но и время программистов.
<?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. Исходный код автозагрузчиков из статьи, а также примеры их использования можно увидеть здесь.
