Подключение файлов в 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
