Подключение файлов в PHP через require, автозагрузка через SPL и использование composer
Любой проект содержит множество файлов, каждый из них может являться автономной единицей или использовать функционал кого-либо еще. Вот о взаимодействиях файлов и поговорим.
Допустим у нас есть проект со следующей файловой структурой:
- commands
- Command1.php
- Command2.php
- Command3.php
- CommandInterface.php
- index.php
Пробежимся по содержимому директории commands. Каждый файл содержит в себе только один класс или интерфейс.
Command1
<?php class Command1 implements CommandInterface { public function execute() { return __METHOD__; } }
Command2 и Command3 отличаются от Command1 только названием класса.
CommandInterface
<?php interface CommandInterface { public function execute(); }
Точкой входа является index.php
и в зависомости от параметра action
он создает экземпляр класса определенной команды и вызывает метод execute
. Допустим он выгляди вот так:
<?php $actionsMap = [ 'create' => 'Command1', 'update' => 'Command2', 'delete' => 'Command3', ]; if (empty($_GET['action']) || !isset($actionsMap[$_GET['action']])) { throw new Exception('Bad request'); } $className = $actionsMap[$_GET['action']]; /** @var CommandInterface $command */ $command = new $className; echo $command->execute();
Загвоздка в том, что файл index.php
не знает где находятся классы Command1
, Command2
и Command3
. Попробуем это побороть.
Require
Так как мы знаем где что располагается, то прибегнем к самому простому варианту - добавим require
наших файлов с командами в самый верх индексного:
<?php $commandsDirectory = __DIR__ . DIRECTORY_SEPARATOR . 'commands' . DIRECTORY_SEPARATOR; require $commandsDirectory . 'Command1.php'; require $commandsDirectory . 'Command2.php'; require $commandsDirectory . 'Command3.php';
Но этого оказывается недостаточно. Мы забыли про то, что команды реализуют CommandInterface
, поэтому надо добавить еще его и обязательно перед подключением команд.
Итак, какие трудности мы встретили на нашем пути?
- PHP не знает где брать какой класс, мы должны ему подсунуть все необходимое
- необходимо держать в голове кто от кого зависит и соблюдать порядок require
Что еще может нам помешать в дальнейшем?
- вызов require одного и того же файла несколько раз породит ошибку
- добавление новых классов и зависимостей окончательно забьет голову тонной информации о связях
Решить первый вопрос поможет конструкция require_once, а вторую автозагрузчик SPL.
spl_autoload
Библиотека SPL содержит ряд функций для работы с автозагрузкой (spl_autoload_*), нам достаточно ограничиться одной - spl_autoload_register
. Для этого создадим файл autoloader.php
в корне проекта. В нем мы укажем правила соотношения названия класса с именем файла в котором он расположен. В нашем случае название класса и файла совпадают, поэтому получится достаточно просто:
<?php function commandsFolderAutoloader($className) { // Путь до файла $fileName = __DIR__ . DIRECTORY_SEPARATOR . 'commands' . DIRECTORY_SEPARATOR . $className . '.php'; // Если файл существует, то вызываем require и говорим spl-автозагрузчику, что все ок if (file_exists($fileName)) { require_once $fileName; return true; } // Все не ок. Мы не знаем, что это за файл return false; } // Регистрируем нашу функцию автозагрузки spl_autoload_register('commandsFolderAutoloader');
А в index.php
заменить все наши require на один require __DIR__ . DIRECTORY_SEPARATOR . 'autoloader.php';
.
Проверяем. Все работает. Таким образом мы можем зарегистрировать несколько функций автозагрузки с произвольными правилами, но какие остались проблемы?
Основная проблема осталась одна - все приведенные решения являются нашими "велосипедами". Так как мы написали очередную реализацию давно решенной проблемы и не пользовались стандартами PSR-0, PSR-4. Поэтому предлагаю использовать автозагрузчик от composer
.
Composer
Composer - это полноценный менеджер пакетов, который поддерживает два стандарта PSR-0 и PSR-4. Он отлично решит нашу проблему и даст задел на будущее при использовании сторонних пакетов.
Осталось только провести некоторые манипуляции:
- устанавливаем композер, если его нет
- выполняем
composer init
в корневой директории проекта - добавляем в созданный
composer.json
секцию автозагрузки{ "name": "MothersEngineer/ComposerExample", "type": "project", "license": "MIT", "require": {}, "autoload": { "psr-0": { "": "commands" } } }
- выполняем
composer install
- замением в
index.php
подключениеrequire __DIR__ . DIRECTORY_SEPARATOR . 'autoloader.php';
наrequire __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
Вот и все. Наше "приложение" работает. Изначально этот вариант может показаться излишне сложным, но он окупится, когда мы начнем использовать сторонние компоненты.
Что почитать по теме
- Установка композера
- Автозагрузчик composer
- Стандарты автозагрузки PSR-0 и PSR-4
- Автоматическая загрузка классов в PHP