Autoload в Composer

Как реализован автозагрузчик классов в Composer и какие возможности он имеет.

Инициализация Composer’a

Composer — это менеджер зависимостей для PHP. С помощью этого инструмента вы можете указать все необходимые библиотеки для вашего проекта и он установит их за вас. Рекомендую ознакомиться с отличной документацией, которая доступна здесь. Сперва нам надо инициализировать Composer в нашем демо проекте (в моем случае корневой папкой проекта является ~/projects/demo-autoload-composer), для чего необходимо создать корректный composer.json файл. Это можно сделать с помощью:

  1. $ composer init

Создание конфига происходит в интерактивном режиме и состоит из следующих шагов:

— определение имени пакета

  1. Package name (<vendor>/<name>) [gremlin/demo-autoload-composer]: fdevs/demoautoloadcomposer

— установка описания пакета

  1. Description []: Demo for article Autoload in Composer for 4devs.io

— обозначение автора, минимальной стабильности пакета и лицензии

  1. Author [vmelnikukraine <melnikvictorl@gmail.com>]:
  2. Minimum Stability []:
  3. License []: MIT

— определение зависимостей нашего пакета

  1. Would you like to define your dependencies (require) interactively [yes]? no
  2. Would you like to define your dev dependencies (requiredev) interactively [yes]? no

Разбор зависимостей не является темой статьи, поэтому просто зададим no и в том и в другом случае.

— подтверждение генерации

  1. Do you confirm generation [yes]?

На каждом шаге Composer в [] скобках показывает значения, которые будут выбраны по умолчанию.

После этого у нас в проекте появится файл composer.json, описывающий наш демо проект:

  1. {
  2. «name»: «fdevs/demo-autoload-composer»,
  3. «description»: «Demo for article Autoload in Composer for 4devs.io»,
  4. «license»: «MIT»,
  5. «authors»: [
  6. {
  7. «name»: «vmelnik-ukraine»,
  8. «email»: «[email protected]»
  9. }
  10. ],
  11. «require»: {
  12.  
  13. }
  14. }

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

  1. $ composer install

Таким образом Composer подтянет все зависимости, указанные в проекте (в данном случае ничего, т.к. секция require в composer.json пустая) и установит сам Composer как стороннюю библиотеку в проект. По умолчанию это папка vendor в корне проекта. Теперь мы можем приступить к изучению автолоадера классов, который предоставляет нам Composer.

Автозагрузчик классов

Чтобы подключить автолоадер от Composer’a в наш демо проект достаточно добавить файл с автолоадером:

  1. <?php
  2.  
  3. // web/index.php
  4.  
  5. // Регистрируем автолоадер Composer’a
  6. require ‘../vendor/autoload.php’;

Как автолоадер Composer’а позволяет загружать файлы? В Composer’е имеется своя JSON-схема, которая описывает весь формат composer.json файла, т.е. все допустимые в нем ключи и их возможные значения. Эта же JSON-схема также служит и для валидации пользовательского composer.json файла. На данном этапе нас интересует лишь один раздел autoload:

  1. «autoload»: {
  2. «type»: «object»,
  3. «description»: «Description of how the package can be autoloaded.»,
  4. «properties»: {
  5. «psr-0»: {
  6. «type»: «object»,
  7. «description»: «This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.»,
  8. «additionalProperties»: true
  9. },
  10. «classmap»: {
  11. «type»: «array»,
  12. «description»: «This is an array of directories that contain classes to be included in the class-map generation process.»
  13. },
  14. «files»: {
  15. «type»: «array»,
  16. «description»: «This is an array of files that are always required on every request.»
  17. }
  18. }
  19. },

По этому разделу JSON-схемы мы видим, что Composer поддерживает три возможных варианта автозагрузки файлов/классов. Давайте разберем их один за другим и подкрепим это все небольшими примерами.

PSR-0

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

  1. src
  2. NS
  3. Badge.php (Класс Badge в неймспейсе NS)
  4. PEAR
  5. Filter
  6. Integer.php (Класс Filter_Integer)

Нам необходимо научить Composer подгружать pear классы и классы с неймспейсами. Давайте сделаем это, добавив в composer.json в autoload следующий ключ:

  1. // composer.json
  2.  
  3. «autoload»: {
  4. «psr-0»: {
  5. «NS\\»: «src/»,
  6. «Filter_»: «src/PEAR»
  7. }
  8. }

Таким образом мы дали понять Composer’у, что неймспейс NS находится в папке src/NS, а pear классы начинающие с Filter_, следует искать в src/PEAR/Filter.

Теперь необходимо выполнить команду:

  1. $ composer install

Благодаря ней обновится автогенерирующийся файл vendor/composer/autoload_namespaces.php, в котором будет php код для регистрации psr-0 классов:

  1. <?php
  2.  
  3. // vendor/composer/autoload_namespaces.php
  4.  
  5. $vendorDir = dirname(dirname(__FILE__));
  6. $baseDir = dirname($vendorDir);
  7.  
  8. return array(
  9. ‘NS\\’ => array($baseDir . ‘/src’),
  10. ‘Filter_’ => array($baseDir . ‘/src/PEAR’),
  11. );

Также для одного неймспейса можно задать более одной корневой папки:

  1. «autoload»: {
  2. «psr-0»: {
  3. «VMelnik\\»: [«src/», «src2/», «src3/»]
  4. }
  5. }

Т.е. если бы у нас был класс User с неймспейсом VMelnik\Entity в папке src2, то Composer искал бы его последовательно, сначала в папке src/VMelnik/Entity, затем в папке src2/VMelnik/Entity, где он бы его нашел и подгрузил, закончив дальнейший поиск.

Осталось рассмотреть использование классов на примере:

  1. <?php
  2.  
  3. // psr-0.php
  4.  
  5. // Загружаем автолоад Composer’a
  6. require ‘../vendor/autoload.php’;
  7.  
  8. use NS\Badge;
  9.  
  10. // Используем автозагрузку по неймспейсу.
  11. // Класс будет загружен с src/NS/Badge.php
  12. $badge = new Badge(‘Хороший читатель’);
  13.  
  14. // Хотите этот бейдж? Прочитайте статью до конца ?
  15.  
  16. // А вот и пример класса с PEAR именованием,
  17. // он будет загружен с src/PEAR/Filter/Integer.php
  18. $intFilter = new Filter_Integer();
  19. $externalData = ‘Not an integer…’;
  20.  
  21. echo ‘filtered: ‘ . $intFilter->filter($externalData);

Плюсы:

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

Classmap

Как следует из описания, classmap позволяет автозагрузчику работать с классами, не следующими PSR-0соглашению. Проще понять на примере:

  1. <?php
  2.  
  3. // web/classmap.php
  4.  
  5. // регистрируем автозагрузчик
  6. require ‘../vendor/autoload.php’;
  7.  
  8. // используем классы не следующие PSR-0 соглашению
  9. $user = new User(‘Victor’);
  10. $task1 = new Task(‘Show how to use classmap in Composer.’);
  11. $task2 = new Task(‘Describe «file» key in Composer autoload property.’, ‘Need good example.’);
  12.  
  13. $user->addTask($task1);
  14. $user->addTask($task2);
  15.  
  16. foreach ($user->getTasks() as $task) {
  17. echo $task . ‘<br />’;
  18. }

Если мы сейчас запустим classmap.php, то получим Fatal Error, т.к. класс User не будет найден по одной простой причине — мы не дали автозагрузчику Composer’a понять, где искать подобные классы. Давайте это исправим. В папке src/NotPSR0 имеется два класса: User и Task. Необходимо указать Composer’у, что классы, не следующие psr-0  необходимо искать в src/NotPSR0, это можно сделать следующим образом:

  1. // composer.json
  2.  
  3. // в секцию autoload добавим ключ classmap,
  4. // а в качестве значения укажем нашу папку
  5.  
  6. «autoload»: {
  7. «psr-0»: {
  8. «NS\\»: «src/»,
  9. «Filter_»: «src/PEAR»
  10. },
  11. «classmap»: [«src/NotPSR0»]
  12. }

Мы закончили? Нет ? Т. к. это classmap, то Composer должен сам явно прописать карту классов в файле vendor/composer/autoload_classmap.php, которая сейчас выглядит следующим образом:

  1. <?php
  2.  
  3. // vendor/composer/autoload_classmap.php
  4.  
  5. $vendorDir = dirname(dirname(__FILE__));
  6. $baseDir = dirname($vendorDir);
  7.  
  8. return array(
  9. );

Как происходит этот процесс добавления? Каждый раз, когда мы запускаем команду composer install или composer update, то Composer обращается к ключу classmap и сканирует все папки на наличие файлов с расширениями .php и .inc и ищет в них классы, параллельно заполняя саму карту в файле vendor/composer/autoload_classmap.php. Давайте же наконец-то запустим сканирование и посмотрим что получится:

  1. $ composer install
  2. Loading composer repositories with package information
  3. Installing dependencies (including requiredev)
  4. Nothing to install or update
  5. Generating autoload files

Главное что мы здесь видим, так это информация о том, что autoload файлы обновлены. Давайте посмотрим, что теперь содержит autoload_classmap.php:

  1. <?php
  2.  
  3. // vendor/composer/autoload_classmap.php
  4.  
  5. $vendorDir = dirname(dirname(__FILE__));
  6. $baseDir = dirname($vendorDir);
  7.  
  8. return array(
  9. ‘Task’ => $baseDir . ‘/src/NotPSR0/Task.php’,
  10. ‘User’ => $baseDir . ‘/src/NotPSR0/User.php’,
  11. );

Как мы видим, Composer просканировал указанную ему папку, и нашел 2 класса, которые он и добавил в карту классов для автозагрузки. Теперь мы можем запустить наш пример снова и он отработает на ура.

Какие плюсы и минусы у данного способа?

Плюсы:

+ позволяет работать с библиотеками и классами, не следующими psr-0 соглашению

Минусы:

— необходима повторная генерация автолоад файла при добавлении каждого класса

Надеюсь с classmap все понятно (если нет — задавайте вопросы в коментариях) и теперь можно идти дальше.

Files

С загрузкой классов разобрались, но что делать если у нас есть просто файлы, содержащие наборы часто используемых функций? У Composer’a есть ответ и на этот вопрос, он предлагает нам ключ files. Здесь мы можем указать список файлов, которые необходимо подгружать при каждом обращении к страницам:

  1. // composer.json
  2.  
  3. // в секцию autoload добавим ключ files,
  4. // а в качестве значения укажем наш файл с набором полезных функций
  5.  
  6. «autoload»: {
  7. «psr-0»: {
  8. «NS\\»: «src/»,
  9. «Filter_»: «src/PEAR»
  10. },
  11. «classmap»: [«src/NotPSR0»],
  12. «files»: [«src/htmlHelpers.php»]
  13. }

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

  1. $ composer install

После запуска данной команды Composer обновит файл vendor/composer/autoload_real.php и расширит в нем метод getLoader, чтобы он подключал указанные нами ранее файлы:

  1. <?php
  2.  
  3. // vendor/composer/autoload_real.php
  4.  
  5. // Generated by Composer
  6.  
  7. class ComposerAutoloaderInit14d9b325920dde1e609b481e2faffc42
  8. {
  9. // различные св-ва и методы…
  10.  
  11. // метод, который интересует нас в данный момент
  12. public static function getLoader()
  13. {
  14. // …
  15.  
  16. // непосредственная регистрация неймспейсов, которую мы рассматривали раньше
  17. $map = require __DIR__ . ‘/autoload_namespaces.php’;
  18. foreach ($map as $namespace => $path) {
  19. $loader->set($namespace, $path);
  20. }
  21.  
  22. // регистрация классов из classmap
  23. $classMap = require __DIR__ . ‘/autoload_classmap.php’;
  24. if ($classMap) {
  25. $loader->addClassMap($classMap);
  26. }
  27.  
  28. // регистрация самого автолоадера
  29. $loader->register(true);
  30.  
  31. // вот и наш файл, указанный в files
  32. // таким образом он будет подгружаться при каждом запросе автолоадера
  33. require $baseDir . ‘/src/htmlHelpers.php’;
  34.  
  35. return $loader;
  36. }
  37. }

В завершение приведу небольшой пример, в котором мы уже можем использовать наши «полезные» функции:

  1. <?php
  2.  
  3. // web/files.php
  4.  
  5. // загружаем автолоадер и тем самым подгружаем наш файл
  6. // с полезными функциями
  7. require ‘../vendor/autoload.php’;
  8.  
  9. // используем их (src/htmlHelpers.php)
  10. echo htmlImg(‘http://symfony.com/logos/symfony_black_03.svg’, ‘Symfony’);

Данная возможность не используется столь часто, но тем не менее является весьма полезной в некоторых случаях. По сути тут можно выделить лишь один плюс, но без минусов:

+ возможность подгружать файлы с функциями

Неужели конец?

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

Чем автолоадер от Composer’a лучше тех, которые мы обсуждали в прошлой статье? Тем что он поддерживает различные варианты автозагрузки классов, объединяя в себе все изученное ранее. Плюс он также позволяет подгружать не только классы, а и наборы функций. И это еще не главное, главное — это его способность подгружать целые библиотеки вместе с их зависимостями, упрощаяя разработку, но это уже тема для отдельной статьи.

P.s.

Если вы правили composer.json, то хорошей практикой перед тем, как закомитить свой код, является проверка этого файла на валидность. Composer поддерживает данный функционал с помощью команды validate, которая использует описанную выше JSON-схему для валидации файла composer.json:

  1. $ composer validate
  2. ./composer.json is valid

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

На этом небольшой рассказ об автолоадере Composer’a можно считать завершенным, спасибо за внимание и приятной вам разработки.