Laravel framework learning — Middleware

Time:2021-2-25

I haven’t written an article for a long time. Let’s record what we have learned during this period
Laravel middleware is a very convenient thing, which can decouple some logic implementation, and in laravel,
The writing of middleware is also very convenient. Whoever uses it knows.

1. Decorator mode

The middleware in laravel uses decorator pattern. What is decorator patternDecorator modeLet’s get to know about it first. This pattern is mainly used to solve the problem that when a class needs to extend its function dynamically, the use of inheritance will make subclasses expand, and when the extended function is a public function, it is not conducive to function reuse and code decoupling.

In laravel, the function of using this mode is called request processing pipeline, or pipeline

//Common interface
interface middleware {
        public static function handle(Closure $next);
    }
//Decorator 1
class MiddleStepOne implements middleware{
        public static function handle(Closure $next) {
            Echo "the first step of preprocessing" "< br >";
            $next();
            Echo "the first step of post-processing" "< br >";
        }
    }
//Decorator 2
class MiddleStepTwo implements middleware{
    public static function handle(Closure $next) {
        Echo "the second step of preprocessing" "< br >";
        $next();
        Echo "the second step of post-processing" "< br >";
    }
}

function goFunc() {
    return function ($step,$className) {
      return function () use ($step,$className) {
          return $className::handle($step);
      };
    };
}
$pip = array(
    MiddleStepOne::class,
    MiddleStepTwo::class,
);
$pip = array_ Reverse ($PIP); // reverses the array to achieve the required order
$first = function (){
    Echo "preprocessing finished" "< br >";
}; // the actual function to be processed
$a = array_ Reduce ($pip, gofunc(), $first); // traverse the PIP array and pass first as the first parameter
$a(); // execute

output
Laravel framework learning -- Middleware

This is a simple pipe based on decorator mode. Its essence is based on closure and recursion.

By analyzing this program, for the final generated $a variable, its value is about this MiddleStepOne.handle ( MiddleStepTwo.handle (first)), when executing, because there is a next() function in the handle, it is a recursive call. For the middleware of laravel, its implementation principle is the same as this one.

2. Middleware and request processing pipeline in laravel

In laravel, we can set middleware to do some pre-processing before the request is executed.

From request entry to public/ index.php start

Laravel framework learning -- Middleware

What’s important is this Code: it processes the request and returns the response to the request
$response = $kernel->handle(

$request = illuminate / HTTP / request:: capture() // create a request instance

);

Next, let’s go to the kernel to see its concrete implementation, illuminate FoundationHttpKernel.php in

Laravel framework learning -- Middleware

Laravel framework learning -- Middleware
About the dispatchtorouter() function, please go and see for yourself.

Next comes the exciting pipeline class,

<?php

namespace Illuminate\Pipeline;

use Closure;
use RuntimeException;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;

class Pipeline implements PipelineContract
{
    /**
     * The container implementation.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * The object being passed through the pipeline.
     *
     * @var mixed
     */
    protected $passable;

    /**
     * The array of class pipes.
     *
     * @var array
     */
    protected $pipes = [];

    /**
     * The method to call on each pipe.
     *
     * @var string
     */
    protected $method = 'handle';

    /**
     * Create a new class instance.
     *
     * @param  \Illuminate\Contracts\Container\Container|null  $container
     * @return void
     */
    public function __construct(Container $container = null)
    {
        $this->container = $container;
    }

    /**
     * Set the object being sent through the pipeline.
     *
     * @param  mixed  $passable
     * @return $this
     */
    public function send($passable)
    {
        $this->passable = $passable;

        return $this;
    }

    /**
     * Set the array of pipes.
     *
     * @param  array|mixed  $pipes
     * @return $this
     */
    public function through($pipes)
    {
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();

        return $this;
    }

    /**
     * Set the method to call on the pipes.
     *
     * @param  string  $method
     * @return $this
     */
    public function via($method)
    {
        $this->method = $method;

        return $this;
    }

    /**
     * Run the pipeline with a final destination callback.
     *
     * @param  \Closure  $destination
     * @return mixed
     */
    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }

    /**
     * Get the final piece of the Closure onion.
     *
     * @param  \Closure  $destination
     * @return \Closure
     */
    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            return $destination($passable);
        };
    }

    /**
     * Get a Closure that represents a slice of the application onion.
     *
     * @return \Closure
     */
    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                if (is_callable($pipe)) {
                    // If the pipe is an instance of a Closure, we will just call it directly but
                    // otherwise we'll resolve the pipes out of the container and call it with
                    // the appropriate method and arguments, returning the results back out.
                    //If PIP means that the middleware function is a closure callable function, just return the closure function directly
                    //I haven't found the corresponding usage scenario here, so I will add it later
                    return $pipe($passable, $stack);
                } elseif (! is_object($pipe)) {
                    list($name, $parameters) = $this->parsePipeString($pipe);

                    // If the pipe is a string we will parse the string and resolve the class out
                    // of the dependency injection container. We can then build a callable and
                    // execute the pipe function giving in the parameters that are required.
                    $pipe = $this->getContainer()->make($name);

                    $parameters = array_merge([$passable, $stack], $parameters);
                } else {
                    // If the pipe is already an object we'll just make a callable and pass it to
                    // the pipe as-is. There is no need to do any extra parsing and formatting
                    // since the object we're given was already a fully instantiated object.
                    $parameters = [$passable, $stack];
                }

                return method_exists($pipe, $this->method)
                                ? $pipe->{$this->method}(...$parameters)
                                : $pipe(...$parameters);
            };
        };
    }

    /**
     * Parse full pipe string to get name and parameters.
     *
     * @param  string $pipe
     * @return array
     */
    protected function parsePipeString($pipe)
    {
        list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);

        if (is_string($parameters)) {
            $parameters = explode(',', $parameters);
        }

        return [$name, $parameters];
    }

    /**
     * Get the container instance.
     *
     * @return \Illuminate\Contracts\Container\Container
     * @throws \RuntimeException
     */
    protected function getContainer()
    {
        if (! $this->container) {
            throw new RuntimeException('A container instance has not been passed to the Pipeline.');
        }

        return $this->container;
    }
}

Generally speaking, the implementation of pipeline class is similar to the modifier I wrote before. The main trouble here is that
In the protected function carry() function, when pip is a closure, a string and an object are handled.

Before, I thought that laravel’s middleware was a very mysterious thing, but after reading it, I found that it was just like that, very delicate, and this mode was also very helpful in the actual development. For example, we used a gateway project, because we didn’t use any framework, so we separated the judgment conditions and wrote them into the middleware, In this way, modular programming is realized to a certain extent.