О HTTP, Middleware и PSR-7 или что не так с текущим подходом

Пока в php.internals идут жаркие дискуссии на тему scalar type hinting, тем временем в мире PSR уже приняли очередной стандарт. Довольно важный. Не хочу чтобы показалось что остальные стандарты не важны, но каждый следующий все более захватывающий.

Этот стандарт описывает как должно выглядеть http-сообщение, которое будет попадать в php скрипт, и которое этот скрипт будет возвращать. На самом деле ничего сверх нового, все сделано по rfc, но просто решили наконец-то реализовать это все php. С понятием middleware в php это не сильно связано, но единый интерфейс для входящих/исходящих данных делает подход с middleware более удобным.

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

Краткий экскурс по истории принятия стандартов:

PSR-0, чтобы не загружать все классы руками через include и require добавили функцию spl_autoload_register которая принимала параметром callback который в свою очередь возвращал имя файла по имени класса.

PSR-1, решили как правильно именовать классы, методы, свойства.

PSR-2, закончили священную войну и решили использовать пробелы вместо табов.

PSR-3, добавили интерфейс по которому должен работать любой Logger. Яркий пример того о чем говорил Мэтт Зандстра:

Программируйте на основе интерфейса, а не его реализации.

PSR-4, ребята подумали что все уже есть и пора улучшать нажитое, в итоге уточнили как должен работать autoloader. По сути это нужно знать при оформлении своих пакетов, всю остальную работу делает composer.

Остальные стандарты находятся на этапе обсуждения.

PSR-5 (draft), тут таки решились привести все блоки комментариев в единый вид, а то будет затруднительно использовать annotations когда нет стандарта комментариев.

PSR-6 (draft), интерфейс кеширования, суть та же что и с PSR-3 но только для кеширования, как по мне то довольно сложный должен быть стандарт, учитывая что разные системы хранят кеш по разному, но в целом самые популярные методы это использовать ключ-значение хранилище типа memcache(d) и redis или sql базу данных, так что пусть хоть работу с ними стандартизируют.

Ну и самый интересный это PSR-7 (draft), суть его точно такая же как и PSR-6 и PSR-3 — добавить интерфейс, но в этот раз это интерфейсы HTTP-сообщений, т.е. теперь можно будет работать с протоколом как с объектами Request и Response. На самом деле во многих фреймворках итак есть эти объекты, у каждого свои, а теперь они будут едины, и можно будет легче многократно использовать компоненты из других приложений.

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

Middleware — это промежуточное ПО, термин имеет очень широкий круг значений, но в контексте php веб-приложений это код который будет по очереди запускаться при каждом запросе. Для любителей Drupal, WordPress и других CMS это будет напоминать модули. По сути это и есть модули, но которые будут работать не поверх ядра, а вместо него.

Example of middleware in php application

Пример того как работает с использованием middleware

Вебсервер запускает index.php в котором идет запуск фреймворка с middleware компонентами. Каждый компонент принимает параметрами два объекта — Request и Response, и исходя из объекта request будет изменять объект response, который после обработки всеми компонентами отправится обратно в браузер. Оба объекта будут наследовать класс HTTP сообщения из PSR-7 и при вызове __toString() вернут текст согласно гипертекстовому протоколу из RFC.

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

Такой подход используется в ruby посредством rack, в python через WCGI, а так же в NodeJS с помощью Connect и ExpressJS. Matthew Weier O’Phinney подробно описал это в своем блоге https://mwop.net/blog/2015-01-08-on-http-middleware-and-psr-7.html. В PHP такой подход уже реализуют несколько фреймворков: Slim, Silex и StackPHP. При чем последний даже не совсем полноценный фреймворк, а скорее удобная обертка.

Поклонникам Drupal могу сказать что похожий подход и реализует компонент HttpKernel, с тем лишь отличием что middleware для него служат классы которые наследуют HttpKernelInterface и каждый такой класс должен возвращать объект Response, а не изменять его в процессе как предлагают другие реализации. Но не стоит особо радоваться, эта привилегия доступна самой коре drupal (т.к. именно кора использует symfony), а не сторонним модулям.
Поклонникам же WordPress можно особо не переживать по пустякам, что-то подобное там появится далеко не скоро, да и не нужно оно там, пусть сначала роутинг нормальный сделают.

Так все-таки, что дает нам такой подход и что не так с предыдущим?

Самый простой и верный ответ это — Абстракция. Да, именно она любимая. Есть даже такая шутка — «любую проблему можно решить с помощью еще одного слоя абстракции, кроме проблемы переизбытка абстракций» (http://developerslife.ru/8729).
Все дело в том что сейчас в мире CMS модули под каждую систему наглухо заточены для работы с ней. Нельзя вот так взять и использоваться модуль из drupal в wordpress, а так хотелось бы views и cck перенести. А такой уровня абстракции позволяет писать модули которые не зависят от приложения. На самом деле разработчики symfony уже давно так делают, предоставляя отдельны компоненты. В ZF2 тоже есть отдельные модули, но это скорее обертки для библиотек, а вот в ZF3 пообещали такой подход. По своему опыту скажу что иметь возможность использовать абсолютно любую библиотеку из github в своем проекте это очень круто! Особенно когда не нужно мучится и дописывать обертки для работы с этими библиотеками. Особенно когда проект поддерживает composer. Ну просто очень круто это.

Если строить приложение средствами middleware подхода то все компоненты можно будет использоваться в разных проектах на разных фреймворках. Нужно записывать логи в файл, выполни composer some-owner/logger и подключи нужный middleware. Нужен кеш для анонимных пользователей загрузи другую библиотеку и используй ее. А если такую же задачу нужно сделать но для другого фреймворка, то пожалуйста, выполни те же действия и все будет работать точно так же. Тогда и споры пропадут о том какой фреймворк лучше, останутся лишь варианты где какой использовать.

Отличный пример того как это работает в Slim фреймворке написан в статье на http://www.sitepoint.com/working-with-slim-middleware/. Только в Slim каждый middleware запускает следующий через $this->next->call() таким образом решая показывать ли следующую часть или отправить пользователя на другую страницу. И этот подход заставляет создателей фреймворка добавить события такие как slim.before и slim.after. Так что тут используется не только один подход. В Silex используется такой же подход с событиями. Но лично мне понравился подход который описал Igor в своем блоге https://igor.io/2013/02/02/http-kernel-middlewares.html, использоваться middleware как декораторы. Но боюсь мой монолог на эту тему может очень растянутся и никто его не прочтет.

Надеюсь я немного разъяснил что за модное слово middleware и что за шумиха была вокруг drupal, symfony и HttpKernel. Пока удобство этого подхода только на словах, пока еще не применял на живых приложениях, по-этому буду рад обсудить этот подход в комментариях.

О HTTP, Middleware и PSR-7 или что не так с текущим подходом: 2 комментария

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *