On the idea of creating laravel Middleware

Time:2022-6-20

Laravel middleware provides a mechanism to interrupt the original program flow, process some events through the middleware, or extend some functions without modifying the logic code. For example, logging middleware can easily record request and response logs without changing logic code.

So let’s simplify the software execution process. Now there is a core class kernel, and the following is its laravel code

#Capture request
$request = Illuminate\Http\Request::capture()
#Process request
$response = $kernel->handle($request);

The code is used to capture a request and return a response. This is the code segment that is subsequently distributed to the specific execution logic and returns the results.

So how do you usually write a piece of logic before or after executing the $kernel->handle () method. It is roughly as follows:

$request = Illuminate\Http\Request::capture()
function midware(){
    Before() \
    #####   
    $response = $kernel->handle($request);
    #####
    After() \
 
}

Obviously, there is no problem in writing this way, but it is not extensible. If you want to implement anything, you need to change this method. This cannot be encapsulated into the core content of the framework. How to improve it

Define a middleware class called middleware to be executed. The class implements two methods, before () and after (), and then the code is as follows.

#One of the configuration items configures the middleware:
middleware = '';
$request = Illuminate\Http\Request::capture()
function midware(){
    middleware.before()
    #####   
    $response = $kernel->handle($request);
    #####
    middleware.after()
}

Has the problem been solved? Has the problem of no change been solved? But what can we do if we need multiple middleware? The easiest thing to think of is to define a middleware array_ Arr, each middleware class contains before and after methods. The code is as follows:

There is Middleware in the configuration item_ arr
middleware_arr=array();
$request = Illuminate\Http\Request::capture()
function midware(){
    foreach(middleware_arr as middleware){
       middleware.before()
    }
    #####   
    $response = $kernel->handle($request);
    #####
    foreach(middleware_arr as middleware){
        middleware.after()
    }
}

It’s a bit old-fashioned, but it does solve the problem. However, there is still a problem about how to transfer parameters to the middleware. Is it OK to:


$request = Illuminate\Http\Request::capture()
function midware(){
    foreach(middleware_arr as middleware){
       middleware.before($request)
    }
    #####   
    $response = $kernel->handle($request);
    #####
    foreach(middleware_arr as middleware){
        middleware.after($response)
    }
}

It seems that the problem has been solved, but after careful analysis, it will be found that the initial $request is given to the middleware every time, which is obviously not good. It is modified as follows:


$request = Illuminate\Http\Request::capture()
function midware(){
    foreach(middleware_arr as middleware){
       $request = middleware.before($request)
    }
    #####   
    $response = $kernel->handle($request);
    #####
    foreach(middleware_arr as middleware){
        $response = middleware.after($response)
    }
}

Another question is, suppose there are two middleware A and B, what is the execution order


$request = Illuminate\Http\Request::capture()
$request = A.before($request);
$request = B.before($request);
$response = $kernel->handle($request);
$response = A.after();
$response = B.after();

Is that reasonable? It’s hard to distinguish. Let’s assume that there is a middleware that records request and response logs. At this time, no matter where you put it, it can’t perfectly record the initial request and final log. Do you want to write two classes in a similar situation? One record request is placed first in the middleware array, and the other processing response is placed last in the array? Why don’t you put the middleware before executing the following foreach_ The ARR array is reversed to meet the requirements:


$request = Illuminate\Http\Request::capture()
$request = A.before($request);
$request = B.before($request);
$response = $kernel->handle($request);
$response = B.after();
$response = A.after();

But I also began to doubt whether this old-fashioned and inflexible solution had a better solution. When I observed the execution sequence, I found that it was a package style (onion style). Can we find a more flexible and elegant solution to the next problem? Looking at the above structure, I always feel a little familiar. It is very similar to a’s function wrapping B’s function. B’s function includes the initial execution code. It is easy to call functions inside functions, but each middleware here does not know the existence of the other. Therefore, it is necessary to transfer the functions to be executed by other middleware to the upper level. Here, the closure function and the PHP function array are used_ reduce(),

array_ Reduce function definition: mixed array_ reduce ( array $input , callable $function [, mixed $initial = NULL ] )


<?php
function  rsum ( $v ,  $w ){
    $v  +=  $w ;
    return  $v ;
}
function  rmul ( $v ,  $w ){
    $v  *=  $w ;
    return  $v ;
}
$a  = array( 1 ,  2 ,  3 ,  4 ,  5 );
$x  = array();
$b  =  array_reduce ( $a ,  "rsum" );
$c  =  array_reduce ( $a ,  "rmul" ,  10 );
?>  

Output:

This will make $b 15 and $c 1200 (= 10*1*2*3*4*5)

array_ Reduce() iteratively applies the callback function function to each cell in the input array, thus simplifying the array into a single value. We wrap multiple functions into one function that will eventually be called.

#Let's assume that there is only one middleware called log to simplify the situation. The class here should be a full path of the class. I'll simply write it here, otherwise it's too long.
$middleware_arr = ['log'];
#Finally, the code to be executed is encapsulated into a closure, otherwise there is no way to pass it to the inner layer. If you pass a function by function name, there is no way to pass parameters.
$default = function() use($request){
    return $kernel->handle($request);
}
$callback = array_reduce($middleware_arr,function($stack,$pipe) {
    return function() use($stack,$pipe){
        return $pipe::handle($stack);
    };
},$default);
#Here, callback is finally a function:
function() use($default,$log){
    return $log::handle($default);
};
#Therefore, every middleware needs a method handle method, in which the transmitted functions are run, such as the following. Here, my class name is not capitalized
class log implements Milldeware {
    public static function handle(Closure $func){
        $func();
    }
}
#It is not difficult to see that the logic of adding middleware itself is as follows:
class log implements Milldeware {
    public static function handle(Closure $func){
        #Here you can run the logical block before()
        $func();
        #Here you can run the logical block after()
    }
}

In this way, when executing the callback function, the execution sequence is as follows:

Run the log:: haddle() method first,

Log:: before() method executed

Run the default method and execute $kernel->handle ($request)

Run the log:: after() method

Then simulate multiple cases as follows:

$middleware_arr = ['csrf','log'];
#Finally, the code to be executed is encapsulated into a closure, otherwise there is no way to pass it to the inner layer. If you pass a function by function name, there is no way to pass parameters.
$default = function() use($request){
    return $kernel->handle($request);
}
$callback = array_reduce($middleware_arr,function($stack,$pipe) {
    return function() use($stack,$pipe){
        return $pipe::handle($stack);
    };
},$default);

#Here, the callback is finally executed as follows:
$log::handle(function() use($default,$csrf){
    return $csrf::handle($default);
});

The execution sequence is as follows:

1. run the log:: haddle (including csrf:: handle closure function) method first,

2. executed the log:: before() method

3. running closures means running $csrf:: handle ($default)

4. the csrf:: before() method is executed

5. run the default method and execute $kernel->handle ($request)

6. executed the csrf:: after() method

7. run the log:: after() method

Note that another problem here is that the results produced by the middleware are not transferred. The same purpose can be achieved by modifying common resources. It is not necessary to transfer values to the next middleware.

This is the end of this document. In fact, many of these joints were only figured out when I wrote this article. In particular, the use and understanding of closure functions have been deepened. Closure functions can delay the use of resources. For example, statements that are not suitable for execution at present have to be passed to the back. Closures can be encapsulated and passed out, which is impossible for traditional functions.

The above is the details of the idea of creating laravel middleware. For more information about the idea of creating laravel middleware, please pay attention to other relevant developeppaer articles!