Как создавать легко поддерживаемые PHP приложения

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

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

Эта статья является перводом статьи Dariusz Gafka "How To Build Maintainable PHP Applications"

https://blog.ecotone.tech/how-to-build-maintainable-php-applications/ 

* Для нужд статьи я буду ссылаться на пакет как на синоним библиотеки или фреймворка.

Подводные камни библиотек и фреймворков

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

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

Чрезмерная конфигурация

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

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

Чужая абстракция

Код, специфичный для вашего бизнеса и решающий его проблемы, является кодом, связанным с бизнесом.

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

Бизнес развивается и меняется, как и системные требования. Если мы решаем бизнес задачи кодом внешних пакетов, мы полагаемся на милость создателей этих пакетов.

Нужно ли нам использовать внешние пакеты для разработки?

Давайте рассмотрим ситуацию, в которой мы вообще не используем никакого фреймворка. Сначала нам нужно создать некоторый объект Request, который будет обрабатывать наш входящий HTTP-запрос. Затем нам нужно будет построить систему маршрутизации, которая будет сопоставлять наш запрос с конкретным действием. Как насчет внедрения зависимостей, нам тоже нужно связать наши классы вместе, верно?

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

Есть множество фреймворков и библиотек, чтобы помочь нам. Они решают для нас множество проблем, поэтому мы можем быстро создавать более безопасные приложения. Мы не можем избежать внешних пакетов. Вопрос в том, как их использовать, чтобы получить пользу и избежать ловушек?

Как использовать Framework, библиотеки и избежать ошибок

Итак, как мы можем использовать Framework и библиотеки таким образом, чтобы они могли помочь нам в долгосрочной поддержке и возможности обновления?

Изолируйте бизнес-ориентированный код

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

Это означает реализовать классы без внешних пакетов, чтобы мы могли расширять и развивать систему на наших условиях.

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

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

Разрешить Framework склеивать ваш код

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

При использовании веб-фреймворка, такого как Symfony и Laravel, мы будем использовать контроллеры и маршрутизацию, поэтому нам не нужно тратить время на создание и подключение HTTP-запроса к определенному классу и действию.

Другой пример — обработчики сообщений. Обработчик сообщений отвечает за обработку конкретного сообщения (класса).

Благодаря этому мы можем создать несвязанное решение, в котором верхние уровни, такие как HTTP-контроллер или команда консольной строки, могут создать сообщение и отправить его, не ссылаясь на конкретный код, связанный с бизнесом.

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

Позволив фреймворку выполнять связующий код, мы можем сосредоточиться и уделять больше времени основным бизнес-функциям.

Используйте простую настройку

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

<?php class OrderHandler { public function handle(PlaceOrderMessage $message): void { // do something } }

Мы хотим зарегистрировать класс OrderHandler и дескриптор метода в качестве нашего обработчика сообщений, который будет обрабатывать класс сообщений PlaceOrderMessage.

Итак, какие варианты у нас есть для регистрации этого обработчика?

1. XML or YAML

Первым решением будет использование XML или YAML. Давайте проверим, как может выглядеть такая конфигурация:

CommandHandlers: - class: OrderHandler method: handle

Мы могли бы добиться хорошего разделения, отделив бизнес-код от связующего кода. Однако у него есть недостаток: в случае любого рефакторинга нам нужно будет отредактировать эти файлы, и это может занять очень много времени. Если вы знакомы с настройкой Dependency Injection с помощью файлов конфигурации, я уверен, что вам знакома эта проблема.

2. Реализация специфического интерфейса Framework

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

<?php class OrderHandler implements MessageHandlerInterface { public function handle(PlaceOrderMessage $message): void { // do something } }

Легко реализовать, так как нам просто нужно реализовать интерфейс или расширить определенный класс. И также легко увидеть, что этот класс является обработчиком сообщений. Однако он создает прямую связь с фреймворком. Это также ограничивает, так как нам нужно будет следовать соглашению, определенному фреймворком, а также мы не сможем зарегистрировать более одного обработчика сообщений в одном классе.

3. PHP-конфигурация

Третьим решением было бы предоставить конфигурацию с использованием PHP.

<?php class FrameworkConfiguration { public function registerOrderHandler(): MessageHandler { return new MessageHandler(OrderHandler::class, "handle"); } }

Это позволяет нам автоматически использовать инструменты рефакторинга для имени класса. Мы также сохранили бы разделение, отделив бизнес-код от связующего кода. Однако в случае модификации имя метода все равно нужно будет изменить вручную. И изнутри класса мы не можем ясно видеть, что он был зарегистрирован как обработчик сообщений.

4. PHP Attributes

Последним решением будет предоставление конфигурации с использованием атрибутов.

<?php class OrderHandler { #[MessageHandler] public function handle(PlaceOrderMessage $message): void { // do something } }

Фреймворк найдет все классы и методы, аннотированные #[MessageHandler]. Это позволяет нам изменить метод и имя класса, а конфигурация останется нетронутой. Мы также предоставляем точную информацию внутри класса, что он зарегистрирован как обработчик сообщений. У нас может быть несколько обработчиков сообщений в одном классе. 

И поскольку атрибуты — это просто комментарии к нашему коду (например, DocBlocks), он сохраняет разделение бизнес-кода и кода фреймворка.

Использование атрибутов PHP сделает ваш код самоописательным и простым для рефакторинга и изменения.

Заключение

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


Я заменил изображения в коде, в остальном перевод практически дословный.
Авторизуйтесь чтобы оставлять комментарии