Всем привет!
В этой статье я хочу рассказать о своем опыте переезда на “отвечающую современным трендам” платформу в одном legacy проекте. Все началось примерно год назад, когда меня перекинули в “старый” (для меня новый) отдел.
До этого я работал с Symfony/Laravel. Перейдя на проект с самописным фреймворком количество WTF просто зашкаливало, но со временем все оказалось не так и плохо.
Во-первых, проект работал. Во-вторых, применение шаблонов проектирования прослеживалось: был свой контейнер зависимостей, ActiveRecord и QueryBuilder.
Плюс, был дополнительный уровень абстракции над контейнером, логгером, работе с очередями и зачатки сервисного слоя(бизнес логика не зависела от HTTP слоя, кое-где логика была вынесена из контроллеров). Далее я опишу те вещи, с которыми трудно было мириться: Сам по себе логгер работал и хорошо. Но были жирные минусы: Максимум, что можно было найти в логах — это текст сообщения. Более подробную информацию об ошибке можно было получить после повторения на стенде разработки. Для дополнительного логирования на боевом окружении приходилось ставить блоки try/catch в нужном методе контроллера. Контейнер зависимостей напоминал Symfony контейнер времен 2.4. Каждый сервис требовал регистрации и описания как его собрать. Имея опыт работы с контейнером laravel, где максимально используется autowiring, хотелось избавиться от рутинных действий. Так же отсутствие autowiring снижало желание программистов писать отдельные сервисы (создавать новые классы) под отдельную бизнес задачу, так как это подразумевало необходимость править конфиг контейнера. При этом всегда есть вероятность ошибиться и потерять еще больше времени. Роутинг был логичен и прост, по мотивам в Yii1.
Адрес вида www.carexchange.ru/awesome_controller/update_car означал выполнение контроллера AwesomeController и метода actionUpdateCar.
Но, к сожалению, были вложенные поддиректории сайта и приходилось создавать url’ы вида
www.carexchange.ru/awesome_controller_for_car_insite_settings_approve/update_car
Это не напрягает, но ограничение странное Роутинг был простым, поэтому отсутствовала возможность генерации url автоматически (зачем усложнять). Это привело к тысячам ссылок, которые были захардкожены и в php и js. Мы, конечно, редко меняем url’ы, но иногда такое случается. И искать их по проекту сложно. С приходом еще одного программиста стали подниматься вопросы о возможности рефакторинга, было желание сделать “по человечнее”. “По человечнее” — читай привычнее для современного разработчика. Читать и поддерживать существующий код было сложно->долго->дорого. После нескольких обсуждений с руководством был получен зеленый флаг и началась работа над proof of concept. С самого начала было принято решение придерживаться современных стандартов. Это и рекомендации PSR, и следование стандартному поведению других фреймворков. Новые разработчики работавшие на любом современном фреймворке должны были понять где найти контроллеры, как собираются сервисы и где писать бизнес логику. Если внимательнее посмотреть на озвученные претензии, то мы заметим: страдает код уровня приложения (контроллеры) и инфраструктурный слой (контейнер).
Бизнес логика была написана отдельно и не зависела от уровня HTTP — ее оставляем как есть. Active Record и QueryBuilder также не трогаем, так как они работали и не сильно отличались от той же doctrine/dbal.  На самом деле выбор тут был не велик. Тащить весь laravel или symfony ради слоя над HTTP нет смысла. А нужные компоненты всегда можно подключить через composer.
Серьезный выбор был между двумя микро-фреймворками: Slim и Zend.
Оба этих фреймворка полностью поддерживают PSR-7 и PSR-11.  Почему не Lumen? Главная причина конечно же в том, что Lumen сложно назвать “микро” вкупе со всем этим добром. Встроить Lumen в существующий проект сложно. Контейнер зависимостей легко не подменишь (необходимо соблюдение контракта illuminate). Контракт PSR-7 фреймворк поддерживает, но все равно зависит от symfony/http-foundation. Сначала я всерьез взялся за Zend. Но потратив 2 дня, посмотрев на реализацию приложения в идеологии "все middleware", увидев как формируется конфиг контейнера, я с ужасом представил как буду объяснять менее опытным разработчикам чем invokables отличается от factories, и когда писать aliases. Перфекционистам и академикам Zend должен прийтись по нраву. Приложение работает через pipeline и middleware. Но я испугался более высокого порога входа, в то время как переезд должен был быть легким, в идеале незаметным. Затем я переключился на Slim. Его внедрение в проект заняло меньше дня. Выбор контроллеров (старого и новго образца) был реализовано через middleware. На Slim и остановился. В далеких планах перейти на pipeline с PSR-15 middleware.  Здесь я просто скажу что остановился на league/container, и попытаюсь объяснить свой выбор. Тут всего 3 варианта:  Кстати Slim движется к выделению реализации HTTP в отдельный пакет(ожидается в ветке 4.0).
Symfony bridge использовать не хотелось по причине лишнего кода и лишней зависимости. Так как Slim ни в чем нас не ограничивает, предпочтение было отдано реализации Zend. Это только увеличило независимость кода приложения от HTTP слоя. Тут ничего кроме monolog в голову не приходит. Его и прикрутили. Во время разработки бывают полезны PHPConsoleHandler и ChromePHPHandler Slim из коробки имеет FastRoute. На его основе появились именованные роуты. Генерация URL реализована через глобальный хелпер (Как здесь) Сейчас наш контроллер выглядит так: Разумеется, в реальном коде вещи вроде $request->getParsedBody()['name'] и new JsonResponse вынесены на еще один уровень абстракции с дополнительными проверками. В качестве бонуса: в зависимости от окружения есть возможность подменять сервисы в контейнере и запускать функциональные тесты. Как вы видите, много практик с идеологией “так проще” позаимствовано из Laravel. Он действительно задает тренды. Приложение получило новый фреймворк уже после того, как проработало 7 лет. Насколько я знаю, старый самописный фреймворк также появился не сразу. И никто не даст гарантий, что мы не захотим сменить фреймворк через 5 лет. Поэтому код писался максимально независимым от выбранного фреймворка. Бизнес логика и прежде не зависела от приложения, а теперь и контроллеры не зависят от фреймворка. Контроллеры зависят от PSR-7 совместимых запросов и возвращают PSR-7 ответы. А собираются контроллеры приложением, зависящим от PSR-11 совместимого контейнера. Slim работает через middleware и добавлять общую логику стало проще (Логирование ошибок приложения, обработка ошибок пользовательского ввода). Autowiring контроллеров прекрасно работает, по сути контроллеры стали сервисами. Кстати здесь можно подсмотреть пример включения autowiring в slim. Конечно, приложение продолжает развиваться и уже перешло на стандартные интерфейсы кэширования и событий, но их внедрение было чуть менее кроваво:).