其实我灰常不喜欢 laravel 这东西,笨重得要死,过度设计,对 ide 不友好,还特别笨重,附带很多你碰都不会碰的东西,还号称优雅的框架。
我是视极简主义追随者,对于这种砌一堆大的东西很是反感,像 koa 这种就很对我胃口。极简,简单得不能再再简单了,四个文件,每个文件加上空白和注释不超过700行代码。
自从看了 tj 大神写的 co,膜拜至极,这才是艺术家写的代码。反观 laravel 提不起胃口,但是无奈 psr 提议的中间件竟然
将 laravel 形式的中间件形式作为标注提议。说实话打心底排斥,提议是人家提议的,反对一票无效。

花了点时间从 laravel 一大堆乱七八槽
的砖头堆了扒出来 laravel 中间执行逻辑,记整理一下。

中间执行代码片段:


    /**
     * 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();
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }
    

laravel 处理中间件路由的代码依赖一个让人看着不爽的 pipe 类。
下面是 pipe 和 核心代码:

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) { // @1
        return function ($passable) use ($stack, $pipe) { // @2
            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.
                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);
        };
    };
}

thunk 函数加 reduce,抽象派。像我这等菜鸟一眼看过去,what the f*ck~!认真读一遍你会发现其实换成递归,一目了然,非得设计成这鬼样子,当然用户用起来没差别,或者更爽,但是代码可读性就另一回事了。很多人可能看这么一段东西即使多看上起眼也不一定能明白它是怎么运行的。我这里解释一下:

要想明白 pipe 的原理首先得知道 array_reduce 这个函数,如果不熟悉可以到官网文档看一下。array_reduce 每一次会从所有middleware 中去一个到 @1 匿名函数给第二个参$pipe。这里的$stack 注意了,他的初始值是 $this->prepareDestination();, 下一次遍历的时候 $stack 将会变成 @2 匿名函数, 其实@2 匿名函数用过 laravel 的人都会调用到它,只是你不知道而已,在我们写的中间件里,就是常常中间件调用到的 $next($request);。 于是乎中间就能一个套一个一直调用下去,就是所谓的洋葱皮。

这样的逻辑完全可以用简单明了的方法来写,当然这种写法也没问题,我只是吐槽一下他的可读性。如果想了解更简单的方法可以看一下我在 couser 里的实现。


2017-11-07 by 拾桑


螃蟹在晨跑
255 声望4 粉丝

Make it work, make it fast, make it best~!