Middleware and Pipeline features in Laravel



Laravel is a truly large and complex system that tries to solve most of the everyday tasks of a web developer in the most elegant way and to collect as many tools as possible and, which is very important, with the most human interface possible.

And today we will talk about one of these tools, and more precisely about its use and implementation by the programmer. The lack of complete documentation, as well as the lack of Russian-language articles and a very small number of foreign articles - pushed me to the decision to uncover a certain veil of secrecy about this interesting possibility of the framework and the choice of this topic as my first article on Habré.

Middleware


This article assumes that the reader is already familiar with the basic use of this framework functionality, so I will not dwell on this point for a long time.

Out of the box Laravel provides us with quite powerful functionality to filter incoming HTTP requests to our application. This is about everyone's favorite (or not) Middleware - the developer encounters these classes rather quickly, even at the “Basics” reading stage of the official documentation, and this is not surprising - Middleware is one of the main and most important bricks, on the basis of which the whole system is built.

Examples of standard user-cases of this component in Laravel are: EncryptCookies / RedirectIfAuthenticated / VerifyCsrfToken , and in the example of a custom implementation, you can use the application localization middleware (setting the required localization based on certain request data) before sending the request further.

Deeper into the abyss


Give up hope, everyone entering here


Well, now, when the main points are over - we can delve into a terrible place for many people - alpha and omega, the beginning and end - of the Laravel sources . Those who reached immediately close the article - do not rush. In fact, in the source code of this framework there is almost nothing really complicated from the conceptual side - the creators obviously try not only to create a clear and convenient interface for working with their brainchild, but they also try very hard to do the same directly at the source code level, which cannot not happy.

I will try to explain the concept of Middleware and Pipeline work as simply and as easily as possible.at the level of code and logic, and I will try not to go deep into it - where it is not necessary within the framework of the article. So, if in the comments there are people who know all the lines of the source code by heart - I will ask to refrain from criticizing my superficial narration. But any recommendations and corrections of inaccuracies are welcome.

Middleware - on the other side of the barricades


I believe that learning anything is always easier when good examples are provided. Therefore, to explore this mysterious beast under the name Pipeline, I suggest we with you. If there really are such brave men - then before further reading we will need to install an empty Laravel project of version 5.7 - the version is due only to the fact that it is the last one at the time of writing, all of the above should be identical at least to version 5.4. Those who just want to know the essence and conclusions of the article - you can safely skip this part.

What could be better than studying the behavior of any component, except for not studying the behavior already embedded in the system? Maybe something can be, but we will manage without unnecessary complications and begin our analysis from the standard Middleware - namely, from the simplest and most understandable of the whole gang - RedirectIfAuthenticated :

RedirectIfAuthenticated.php
classRedirectIfAuthenticated{
    /** Выполнить действия со входящим запросом
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */publicfunctionhandle($request, Closure $next, $guard = null){
        if (Auth::guard($guard)->check()) {
            return redirect('/');
        }
        return $next($request);
    }
}


In any classical middleware class, there is a main method that must directly process the request and transfer the processing to the next one in the chain — in our case, the handle method . In this particular class, processing the request is quite simple - “if the user is authorized, then redirect him to the main page and, thereby, stop the execution of the chain”.

If we look at the registration of this Middleware in app / Http / Kernel.php , then we will see that it is registered in 'route middleware'. To find out how the system works with this middleware - let's move to the class from which our app / Http / Kernel is inherited - and it inherits from the Illuminate \ Foundation \ Http \ Kernel class. At this stage, we are directly opening the gates to hell the source code of our framework, or rather, to the most important and its main part, to the core of work with HTTP. By the way, who cares - Laravel is based on many components of symfony , specifically in this part - on HttpFoundation and HttpKernel .

The definition and implementation of our middleware in the kernel constructor is as follows:

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
     */publicfunction__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);
        }
    }


The code is quite simple and clear - for each middleware in the array, we register it with an alias / index in our router. The methods aliasMiddleware and middlewareGroups of our Route class are simply adding middleware to one of the arrays of the router object. But this is not in the context of the article, so let's skip this point and move on.

What we are really interested in is the sendRequestThroughRoute method , which literally translates to how to send a Request through the Route :

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
     */protectedfunctionsendRequestThroughRouter($request){
        // * пропущена часть кода *return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }


As a parameter, this method receives a request. At this point, we should look again at the code of our RedirectIfAuthenticated . In the handle method of our middleware, we also receive a request, we will need this note a little later.

The code above has a very clear and readable interface - “Pipeline”, which sends a request through each of the registered middleware, and then “sends” it to the router . Charming and wonderful. I think at this stage we will not try to decompose this section of the code further, I will only briefly describe the role of this section in the entire system:

Before getting a request to your controller, a lot of actions take place, starting from simple parsing of the url itself and ending with the initialization of the classRequest . Middleware is also involved in this chain of action. Middleware classes directly implement the (almost) design pattern of the Chain of Responsibility or Chain of Responsibility , so each specific class of midleware is only a link in this chain.

Above, we have not just returned to our initially considered RedirectIfAuthenticated class . The request "circulates" along the chain, including it passes through all the middleware required for the route. This moment will help us with working with our own links of our own chain, more on that later.

Pipeline - sewage of our application


One of the examples of the implementation of Pipeline, we have seen above. But the purpose of the article was not only to explain the operation of this component at the level of integration with Laravel, but also to explain the basic principle of working with this class in our own code.

The class itself can be found by its full definition with namespace:
Illuminate \ Pipeline \ Pipeline

This component can be quite a lot of applications, depending on the specific task that you need to solve, but one of the most obvious motivations is the requirement to create your own chain of request handlers that does not interfere with the processes of the entire system and is determined solely at the level of your business logic. Also, the class interface has a sufficient level of abstraction and has enough functionality to implement various types of queues.

Laravel implementation example


We implement the most simple and remote from the reality chain of requests. As data, we will use the string "HELLO WORLD", and with the help of two handlers, we will form the string "Hello User" from it. The code is intentionally simplified.

Before the direct implementation of our own "Pipe", we need to identify the elements of this pipe. Elements are written by analogy with middleware:

Defining Handlers
StrToLowerAction.php:
useClosure;
classStrToLowerAction{
    /**
     * Handle an incoming request.
     *
     * @param  string $content
     * @param  Closure  $next
     * @return mixed
     */publicfunctionhandle(string $content, Closure $next){
        $content = strtolower($content);
        return $next($content);
    }
}

SetUserAction.php:

useClosure;
classSetUserAction{
    /**
     * Handle an incoming request.
     *
     * @param  string $content
     * @param  Closure  $next
     * @return mixed
     */publicfunctionhandle(string $content, Closure $next){
        $content = ucwords(str_replace('world', 'user', $content));
        return $next($content);
    }
}


Then we create a “pipeline”, determine what data we want to send for it, determine through which collection of handlers we want to send this data, and also define a callback that receives our data passed through the entire chain as an argument. In the case when the data along the chain we remain unchanged - the part with the callback can be omitted:

$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

Also, if you have a desire or need to define your own method in handlers, the Pipeline interface provides a special method via ('method_name') , then the processing of the chain can be written like this:

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

Directly, the data that we pass through processors can be absolutely anything, as well as interaction with them. Type hinting and setting the type of the object returned in the chain will help to avoid data integrity errors.

Conclusion


Laravel provides a large number of built-in classes, and the flexibility of many of them allows us to develop something complicated with sufficient simplicity. This article examined the possibility of creating simple queues for requests based on the Pipeline class built into Laravel. Implementations of this class in the final code can be completely different, and the flexibility of this tool allows you to get rid of many unnecessary actions when building certain algorithms.

How specifically to use this feature of the framework depends on the tasks assigned to you.

Also popular now: