Советы PHP-разработчика

Блог содержащий короткие заметки касающиеся программирования для web.

Подключение файлов в 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. Он отлично решит нашу проблему и даст задел на будущее при использовании сторонних пакетов.

Осталось только провести некоторые манипуляции:

  1. устанавливаем композер, если его нет
  2. выполняем composer init в корневой директории проекта
  3. добавляем в созданный composer.json секцию автозагрузки
    {
        "name": "MothersEngineer/ComposerExample",
        "type": "project",
        "license": "MIT",
        "require": {},
        "autoload": {
            "psr-0": {
                "": "commands"
            }
        }
    }
    
  4. выполняем composer install
  5. замением в index.php подключение require __DIR__ . DIRECTORY_SEPARATOR . 'autoloader.php'; на require __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';

Вот и все. Наше "приложение" работает. Изначально этот вариант может показаться излишне сложным, но он окупится, когда мы начнем использовать сторонние компоненты.

Что почитать по теме

  • 2017-11-16 08:46:53