laravel中间件原理解析

797 阅读1分钟

前置知识

中间件作用

在执行具体controller之前做一些预处理,比如权限判断,参数校验等等

array_reduce()

array_reduce — Iteratively reduce the array to a single value using a callback function

官方文档

这里要掌握array_reduce()方法的原理, 文档的第二个node写的很清楚.原理如下:

<?php
function array_reduce($array, $callback, $initial=null)
{
    $acc = $initial;
    foreach($array as $a)
        $acc = $callback($acc, $a);
    return $acc;
}
?>

中间件实现

本质是通过栈的概念来实现的,逆序将注册的中间件压入栈,弹出时注入(传参等方式)全局request,最后弹出执行业务逻辑,进入response阶段.

实现方法1 遍历数组

gin框架的中间件实现,可以看到就是一个循环

github.com/gin-gonic/gin@v1.6.3/context.go 158

func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

练习:

<?php
$context = [
    'p' => 0,
    'r' => 'hello',
];
$a = function ($context) {
    echo "a start" . PHP_EOL;
    $context['p']++;
    echo "a end" . PHP_EOL;
};
$b = function ($context) {
    echo "b start" . PHP_EOL;
    $context['p']++;
    echo "b end" . PHP_EOL;
};
$stack = [$b, $a];
while ($r = array_pop($stack)) {
    $r($context);
}

实现方法2 闭包

laravel就是通过闭包实现的.

练习:

<?php
//栈式调用
$carry = function ($stack, $pipe) {
    echo 'carry ' . $pipe . PHP_EOL;
    var_dump($stack);
    return function (...$arg) use ($stack, $pipe) {
        echo "carry closure start " . $pipe . PHP_EOL;
//        echo "carry closure run arg = " . var_export($arg, true) . PHP_EOL;
        $stack($arg);
        echo "carry closure end " . $pipe . PHP_EOL;
    };

};

$default = function ($a = 'default') {
    echo "default func" . PHP_EOL;
};
$pips = [
    'apple',
    'banana',
    'candy',
    'doctor',
];

$first = array_reduce(array_reverse($pips), $carry, $default);
$first(1, 2, 3);

找到一个grpc的interceptor的递归实现讲解文章,也是异曲同工 gRPC拦截器那点事,希望帮到你

laravel源码

vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php

 /**
     * Send the given request through the middleware / router.
     *
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        $this->bootstrap();

        /** @var  $pipline \Illuminate\Routing\Pipeline */
        $pipline = new Pipeline($this->app);
        return $pipline
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());
    }

$this->dispatchToRouter()获得路由逻辑和执行方法的闭包,在then方法里作为栈底调用.中间件逻辑在then方法里

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

pipes是中间件类名数组,carry方法返回闭包,最后一个是路由实现的闭包

这里的关键就是array_reduce的作用,最后调用由闭包组成的栈.

 /**
     * 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) {
                try {
                    if (is_callable($pipe)) {
                        // If the pipe is a callable, then we will call it directly, but otherwise we
                        // will resolve the pipes out of the dependency container and call it with
                        // the appropriate method and arguments, returning the results back out.
                        return $pipe($passable, $stack);
                    } elseif (! is_object($pipe)) {
                        [$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];
                    }

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

                    return $this->handleCarry($carry);
                } catch (Throwable $e) {
                    return $this->handleException($passable, $e);
                }
            };
        };
    }

闭包中的$stack就是闭包实现的调用栈,$pipe会在回调方法中运行.至此分析结束.