Главная » Хабрахабр » [Из песочницы] 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 Интересное!

Юбилейный Android 10 (Q). Что известно уже сейчас?

Совсем недавно ребята из XDA-developers заглянули под капот утекшей сборки новой, ещё не анонсированной, версии Android. Давайте посмотрим, что они там нашли. 1. Тёмная тема Давно ждёте тёмную тему? Пожалуйста. Будет добавлена возможность выбрать её прямо в настройках экрана (Display). ...

[Перевод] Учебный курс по React, часть 10: практикум по работе со свойствами компонентов и стилизации

Сегодня, в десятой части перевода учебного курса по React, мы предлагаем вам выполнить практическое задание по работе со свойствами компонентов и по их стилизации. → Часть 1: обзор курса, причины популярности React, ReactDOM и JSX→ Часть 2: функциональные компоненты→ Часть ...