Главная » Хабрахабр » [Из песочницы] Middleware и возможности Pipeline в Laravel

[Из песочницы] Middleware и возможности Pipeline в Laravel

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

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

Middleware

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

Из коробки Laravel предоставляет нам достаточно мощный функционал фильтрации входящих HTTP запросов к нашему приложению. Речь идет о всеми любимых (или нет) Middleware — с данными классами разработчик на пути освоения Laravel сталкивается достаточно быстро, еще на этапе чтения «The Basics» (Основы) пункта официальной документации, и это не удивительно — Middleware является одним из основных и важнейших кирпичиков, на основе которых строится вся система.

Примерами стандартных юз-кейсов этого компонента в Laravel являются: EncryptCookies/RedirectIfAuthenticated/VerifyCsrfToken, а в пример пользовательской реализации можно привести middleware локализации приложения (установки требуемой локализации на основе определенных данных запроса), перед передачей запроса дальше.

Глубже в бездну

Оставь надежду, всяк сюда входящий

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

Так что, если в комментариях найдутся люди, знающие все строчки исходников наизусть — попрошу воздержаться от критики моего поверхностного повествования. Я постараюсь максимально просто и доступно объяснить концепцию работы Middleware и Pipeline на уровне кода и логики, и постараюсь не углубляться туда — куда не нужно в рамках статьи. Но любые рекомендации и исправления неточностей — лишь приветствуются.

Middleware — по ту сторону баррикад

Я верю, что изучение чего бы то ни было всегда дается проще, когда предоставляются хорошие примеры. Поэтому изучить этого таинственного зверя под именем Pipeline я предлагаю нам с вами вместе. Если действительно найдутся такие храбрецы — то перед дальнейшим чтением нам потребуется установить пустой проект Laravel версии 5.7 — версия обусловлена лишь тем, что она последняя на момент написания статьи, всё перечисленное должно быть идентично как минимум до версии 5.4. Те же, кто хочет просто узнать суть и выводы статьи — можете смело пропускать эту часть.

Возможно что-то и может, но мы обойдемся без излишних усложнений и начнем наш разбор со стандартного Middleware — а именно с самого простого и понятного из всей банды — RedirectIfAuthenticated: Что может быть лучше, чем изучение поведения какого-либо компонента, кроме как не изучение поведения уже встроенного в систему?

RedirectIfAuthenticated.php

class RedirectIfAuthenticated
return $next($request); }
}

В любом классическом middleware классе существует главный метод, который непосредственно и должен обработать запрос, и передать обработку следующему в цепочке — в нашем случае — это метод handle. В этом конкретном классе обработка запроса достаточно проста — «если пользователь авторизован — то перенаправить его на главную страницу и, тем самым, прекратить выполнение цепочки».

Чтобы нам узнать как же система работает с этим middleware — перейдем в класс, от которого наш app/Http/Kernel наследуется — а наследуется он от класса Illuminate\Foundation\Http\Kernel. Если мы посмотрим на регистрацию этого Middleware в app/Http/Kernel.php, то мы увидим, что он зарегистрирован в 'route middleware'. Кстати, кому интересно — Laravel базируется на многих компонентах Symfony, конкретно в этой части — на HttpFoundation и HttpKernel. На данном этапе мы с вами непосредственно открываем врата в ад исходный код нашего фреймворка, а точнее — в самую важную и основную его часть — в ядро работы с HTTP.

Определение и реализация наших middleware в конструкторе ядра происходит следующим образом:

Illuminate\Foundation\Http\Kernel(Application $app, Router $router)

/** Создать новый объект HTTP Kernel класса. * Create a new HTTP kernel instance. * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Routing\Router $router * @return void */ public function __construct(Application $app, Router $router) { $this->app = $app; $this->router = $router; $router->middlewarePriority = $this->middlewarePriority; foreach ($this->middlewareGroups as $key => $middleware) { $router->middlewareGroup($key, $middleware); } foreach ($this->routeMiddleware as $key => $middleware) { $router->aliasMiddleware($key, $middleware); } }

Код достаточно простой и понятный — для каждого middleware в массиве мы регистрируем его с алиасом/индексом в нашем роутере. Сами методы aliasMiddleware и middlewareGroups нашего Route класса — это простое добавление middleware в один из массивов объекта роутера. Но это не входит в контекст статьи, поэтому пропустим данный момент и двинемся дальше.

Что нас действительно интересует, так это метод sendRequestThroughRoute, дословно переводящийся, как отправитьЗапросЧерезРоут:

Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter($request)

/** Отправить конкретный запрос через middleware / router. * Send the given request through the middleware / router. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ protected function sendRequestThroughRouter($request) { // * пропущена часть кода * return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }

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

Прелестно и замечательно. Код выше имеет очень понятный и читаемый интерфейс — «Трубопровод», который отправляет запрос через каждый из зарегистрированных middleware, а затем «передает» его в роутер. Я думаю на этом этапе мы не будем пытаться декомпозировать данный участок кода дальше, я лишь вкратце опишу роль этого участка во всей системе:

Middleware в этой цепочке действий также участвует. Перед попаданием запроса в ваш контроллер — проходит достаточно много действий, начиная от простого парсинга самой url, и заканчивая инициализацией класса Request. Непосредственно классы middleware реализуют (почти) паттерн проектирования Цепочка обязанностей или Chain of Responsibility, таким образом каждый конкретный класс midleware — это лишь звено в этой цепочке.

Запрос «циркулирует» по цепи, в том числе он проходит и через все, требуемые для роута middleware. Выше мы не просто так вернулись в наш изначально рассматриваемый класс RedirectIfAuthenticated. Этот момент поможет нам с работой со своими собственными звеньями своей собственной цепи, об этом дальше.

Pipeline — канализация нашего приложения

Один из примеров реализации Pipeline мы видели выше. Но целью статьи было не только объяснение работы этого компонента на уровне интеграции с Laravel, а и объяснение базового принципа работы с этим классом в нашем собственном коде.

Сам класс можно найти по его полному определению с неймспейсом:

Illuminate\Pipeline\Pipeline

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

Пример реализации в Laravel

Реализуем максимально простую и отдаленную от реальности цепочку запросов. В качестве данных мы будем использовать строку " HELLO WORLD", и с помощью двух обработчиков мы сформируем из нее строку «Hello User». Код намеренно упрощен.

Элементы пишутся по аналогии с middleware: Перед непосредственной реализацией нашей собственной «Трубы», нам нужно определить элементы этой трубы.

Определение обработчиков

StrToLowerAction.php:

use Closure;
class StrToLowerAction
{ /** * Handle an incoming request. * * @param string $content * @param Closure $next * @return mixed */ public function handle(string $content, Closure $next) { $content = strtolower($content); return $next($content); }
}

SetUserAction.php:

use Closure;
class SetUserAction
{ /** * Handle an incoming request. * * @param string $content * @param Closure $next * @return mixed */ public function handle(string $content, Closure $next) { $content = ucwords(str_replace('world', 'user')); return $next($content); }
}

Затем мы создаем «трубопровод», определяем что за данные мы хотим по нему отправить, определяем через какую коллекцию обработчиков мы хотим эти данные отправить, а также определяем callback, который получает в качестве аргумента наши данные, пройденные через всю цепочку. В том случае, когда данные на протяжении цепочки у нас остаются неизменными — часть с callback'ом можно опустить:

$pipes = [ StrToLowerAction::class, SetUserNameAction::class
]; $data = 'Hello world'; $finalData = app(Pipeline::class) ->send($data) // Данные, которые мы хотим пропустить через обработчики ->through($pipes) // Коллекция обработчиков ->then(function ($changedData) { return $changedData; // Возвращаются данные, пройденные через цепочку }); var_dump($finalData); // Возвращенные данные записаны в переменную $finalData

Также, если у вас есть желание или потребность определить свой собственный метод в обработчиках, интерфейс Pipeline предоставляет специальный метод via('method_name'), тогда обработка цепи может быть написана таким образом:

$finalData = app(Pipeline::class) ->send($data) ->through($pipes) ->via('handle') // Здесь может быть любое название метода, вы должны гарантировать его наличие во всех обработчиках ->then(function ($changedData) { return $changedData; });

Непосредственно данные, которые мы проводим через обработчики — могут быть абсолютно любыми, как и взаимодействие с ними. Тайп хинтинг и установка типа возвращаемого в цепочке объекта поможет избежать ошибок с целостностью данных.

Заключение

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

Как конкретно использовать данную возможность фреймворка — зависит от поставленных перед вами задач.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Получаем музыку Вк через сторонний API

В этот раз дело начиналось после закрытия методов audio в методе execute. Я решил посмотреть, как получают музыку сайты, которые предоставляют возможность ее скачать. Меня заинтересовал сайт vrit.me. Я залез во вкладку network и увидел интересный запрос: То есть, можно ...

Все, что нужно знать о стрессе и сильных эмоциях

Под катом — цикл из 3-х видео и текстовых версий к ним. Причина внутри нас. Главное — стресс вызван не внешними событиями. Мы можем управлять этим процессом, потому что он основан на нашем опыте. Внутри каждого существует процесс, который отвечает ...