Механизм автозагрузки классов в PHP

В данной статье я хочу рассказать как обстоит дело с автозагрузкой классов в PHP, а также о распространенных подходах при реализации автозагрузки.

Старый добрый PHP

В старом PHP коде обычной практикой было использование функций require, require_once, include, include_once для подгрузки файлов, содержащих необходимые классы, к примеру:

  1. <?php
  2.  
  3. require_once ‘src/User.php’;
  4.  
  5. $user = new User(‘Victor’);

таких require_once в начале файлов скапливалось огромное количество.

Плохо ли это? Да.

Почему? Потому что:

— файлы подгружались в любом случае, даже тогда когда в этом не было никакой необходимости
— постоянно приходилось писать require/include и можно было запросто забыть подключить какой-либо файл
— в случае перемещения какого-либо файла, приходилось менять все относящиеся к нему require

Изменения в PHP 5

Те времена давно прошли, т.к. в PHP 5 появился механизм автозагрузки классов и функция __autoload(),которая вызывается каждый раз, когда создается объект неизвестного класса. Единственное что оставалось разработчику, так это реализовать ее, и больше не было необходимости писать require и т.д:

  1. <?php
  2.  
  3. function __autoload($class)
  4. {
  5. require_once «src/$class.php»;
  6. }
  7.  
  8. // далее можно просто создавать объекты
  9.  
  10. $user = new User(‘Victor’);

т.к. ранее нигде не был подключен файл, содержащий класс User, то будет вызвана функция __autoload(), которой будет передано имя класса как параметр $class, и она в свою очередь попробует подключить файл содержащий данный класс.

С версии PHP 5.1.2 доступна новая функция spl_autoload_register()

  1. bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )

Данная функция позволяет регистрировать любую переданную ей функцию как реализацию механизма автозагрузки классов. Если же вызвать ее без параметров, то в качестве реализации автозагрузки будет выступать функция spl_autoload()

Этот механизм имеет свою очередь, поэтому можно регистрировать более одной функции для разрешения вопросов автозагрузки классов. Все функции будут вызваны в порядке их регистрации. Также с версии 5.3 добавлена поддержка неймспейсов.

Что мы имеем?

+ удобный механизм для автозагрузки классов с уже готовой реализацией по умолчанию

+ файлы загружаются лишь тогда, когда это необходимо

+ нет необходимости писать кучу require

Варианты реализации автозагрузки классов

Предлагаю ознакомиться с распространенными подходами в реализации автозагрузчиков и написать свои.

Ручная регистрация классов для автозагрузки

Данный способ предполагает создание одного автозагрузчика с возможностью регистрации классов в нем. Это может происходить следующим образом:

  1. <?php
  2.  
  3. // src/Autoloader/MapAutoloader.php
  4.  
  5. /**
  6. * Наш автозагрузчик классов
  7. */
  8. class MapAutoloader
  9. {
  10. // карта соответствий названий классов и файлов где они хранятся
  11. protected $classesMap = array();
  12. public function registerClass($className, $absolutePath)
  13. {
  14. if (file_exists($absolutePath)) {
  15. $this->classesMap[$className] = $absolutePath;
  16. return true;
  17. }
  18. return false;
  19. }
  20. public function autoload($class)
  21. {
  22. if (!empty($this->classesMap[$class])) {
  23. require_once $this->classesMap[$class];
  24. return true;
  25. }
  26. return false;
  27. }
  28. }

далее необходимо зарегистрировать наш автозагрузчик:

  1. <?php
  2.  
  3. // examples/map.php
  4.  
  5. $srcDir = __DIR__ . ‘/../src’;
  6.  
  7. require_once $srcDir . ‘/Autoloader/MapAutoloader.php’;
  8. $autoloader = new MapAutoloader();
  9.  
  10. // регистрируем наш автозагрузчик
  11. spl_autoload_register(array($autoloader, ‘autoload’));

Приступим к его непосредственному использованию. Например у нас есть два класса User и Task, которые находятся соответственно в /src/Model/User.php и /src/Model/Task.php, тогда чтобы включить их автозагрузку, нам необходимо сделать следующее:

  1. <?php
  2.  
  3. // examples/map.php
  4.  
  5. $autoloader->registerClass(‘User’, $srcDir . ‘/Model/User.php’);
  6. $autoloader->registerClass(‘Task’, $srcDir . ‘/Model/Task.php’);

потом в любом месте, где нам понадобится создать объекты этих классов, достаточно будет просто написать:

  1. <?php
  2.  
  3. // examples/map.php
  4.  
  5. $user = new User(‘Victor’, ‘Melnik’);
  6. $task = new Task(‘Write about autoloaders in PHP’);
  7.  
  8. echo $user . ‘ task is: «‘ . $task . ‘»‘;
  9.  
  10. // результат:
  11. // Melnik Victor task is: «Write about autoloaders in PHP»

и сработает метод autoload нашего автозагрузчика.

Данный способ не идеален и имеет свои достоинства:

+ простота реализации

+ вместо постоянных require достаточно один раз зарегистрировать класс в автозагрузчике

+ файлы подгружаются только по мере их необходимости

+ в случае изменения местоположения файла, достаточно изменить его путь в одном месте

+ позволяет добавлять сторонние библиотеки в проект, для этого нужно лишь добавить их классы в карту автолоадера

и недостатки:

— необходимо регистрировать классы вручную

— не позволяет по имени класса быстро определить где он находится

Имена классов как указатели пути к файлу

Данный способ автозагрузки классов используется в шаблонизаторе Twig, а также использовался в Zend Framework’e первой версии. Суть его в том, что само имя класса указывает на папку, где находится файл, содержащий этот класс. Это легко понять на примере:

  1. <?php
  2.  
  3. // src/Model/Article.php
  4.  
  5. class Model_Article
  6. {
  7.  
  8. protected $title;
  9. protected $content;
  10.  
  11. public function __construct($title)
  12. {
  13. $this->title = $title;
  14. }
  15.  
  16. // геттеры и сеттеры…
  17. }

На основе данного примера легко написать функцию для автозагрузки классов, следующих подобному соглашению:

  1. <?php
  2.  
  3. // src/Autoload/underscore.php
  4.  
  5. function __autoload($class)
  6. {
  7. $baseDir = __DIR__ . ‘/../’;
  8. $path = $baseDir . str_replace(‘_’, ‘/’, $class) . ‘.php’;
  9. if (file_exists($path)) {
  10. require_once($path);
  11. return true;
  12. }
  13. return false;
  14. }

Вся его работа сводится к одному действию — замене подчеркиваний в названии класса на прямой слеш, формируя таким образом путь к файлу. В данном примере жестко указана корневая точка отсчета начала пути в файловой системе, улучшение этого момента я оставляю на совести читателя.

Пример использования:

  1. <?php
  2.  
  3. // examples/underscore.php
  4.  
  5. // загружаем функцию для автолоада классов
  6. require_once __DIR__ . ‘/../src/Autoloader/underscore.php’;
  7. // использование
  8. $article = new Model_Article(‘Autoloaders in PHP’);
  9. $article->setContent(‘Some very interesting stuff here…’);
  10.  
  11. echo $article->getTitle();

Рассмотрим преимущества и недостатки данного автозагрузчика.

Плюсы:

+ нет необходимости вручную регистрировать классы

+ файлы подключаются только по мере необходимости

+ по имени файла легко определить где он находится

+ исключается возможность конфликтов в именах классов, т.к. не может быть два файла с одинаковым именем в файловой системе

Минусы:

— нет поддержки автозагрузки классов, не следующих данному соглашению при именовании классов, например нет поддержки неймспейсов

— в случае серьезной реорганизации структуры папок придется везде переписывать имена классов

Автозагрузка классов, использующих неймспейсы

С момента появления неймспейсов в PHP данный способ стал одним из самых популярных. Суть в том, что неймспейс необходимо связать с определенной папкой в файловой системе, а все вложенные папки/файлы будут подхватываться автоматически. Ниже приведу пример упрощенного автозагрузчика, поддерживающего работу с неймспейсами:

  1. <?php
  2.  
  3. // src/Autoloader/NamespaceAutoloader.php
  4.  
  5. class NamespaceAutoloader
  6. {
  7. // карта для соответствия неймспейса пути в файловой системе
  8. protected $namespacesMap = array();
  9. public function addNamespace($namespace, $rootDir)
  10. {
  11. if (is_dir($rootDir)) {
  12. $this->namespacesMap[$namespace] = $rootDir;
  13. return true;
  14. }
  15. return false;
  16. }
  17. public function register()
  18. {
  19. spl_autoload_register(array($this, ‘autoload’));
  20. }
  21. protected function autoload($class)
  22. {
  23. $pathParts = explode(‘\\’, $class);
  24. if (is_array($pathParts)) {
  25. $namespace = array_shift($pathParts);
  26. if (!empty($this->namespacesMap[$namespace])) {
  27. $filePath = $this->namespacesMap[$namespace] . ‘/’ . implode(‘/’, $pathParts) . ‘.php’;
  28. require_once $filePath;
  29. return true;
  30. }
  31. }
  32. return false;
  33. }
  34. }

Далее рассмотрим на примере использование этого автозагрузчика:

  1. <?php
  2.  
  3. // examples/namespace.php
  4.  
  5. require_once __DIR__ . ‘/../src/Autoloader/NamespaceAutoloader.php’;
  6. // задаем соответствие для неймспейса Model и регистрируем автозагрузчик
  7. $autoloader = new NamespaceAutoloader();
  8. $autoloader->addNamespace(‘Model’, __DIR__ . ‘/../src/Model’);
  9. $autoloader->register();
  10.  
  11. // способ также поддерживает алиасы
  12. use Model as Alias;
  13.  
  14. $robot = new Alias\Robot(‘Bender’);
  15. $robot->extendVocabulary(«Lets face it, comedy’s a dead art form. Now tragedy! Ha ha ha, that’s funny.»);
  16. $robot->extendVocabulary(«Congratulations, Fry! You snagged the perfect girlfriend. Amy’s rich, she probably has got other characteristics…»);
  17. $robot->extendVocabulary(«I’m a real toughie!»);
  18.  
  19. 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, т.к. он позволяет загружать любые классы.