Laravel 源码详解

 主页   资讯   文章   代码   电子书 

前言

当进行了路由匹配与路由参数绑定后,接下来就要进行路由闭包或者控制器的运行,在此之前,本文先介绍中间件的相关源码。

中间件的搜集

由于定义的中间件方式很灵活,所以在运行控制器或者路由闭包之前,我们需要先将在各个地方注册的所有中间件都搜集到一起,然后集中排序。

public function dispatchToRoute(Request $request)
{
    $route = $this->findRoute($request);

    $request->setRouteResolver(function () use ($route) {
        return $route;
    });

    $this->events->dispatch(new Events\RouteMatched($route, $request));

    $response = $this->runRouteWithinStack($route, $request);

    return $this->prepareResponse($request, $response);
}

protected function runRouteWithinStack(Route $route, Request $request)
{
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;

    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

public function gatherRouteMiddleware(Route $route)
{
    $middleware = collect($route->gatherMiddleware())->map(function ($name) {
        return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
    })->flatten();

    return $this->sortMiddleware($middleware);
}

路由的中间件大致有两个大的来源:

  • 在路由的定义过程中,利用关键字 middleware 为路由添加中间件,这种中间件都是在文件 App\Http\Kernel$middlewareGroups$routeMiddleware 这两个数组定义的中间件别名。
  • 在路由控制器的构造函数中,添加中间件,可以在这里定义一个闭包作为中间件,也可以利用中间件别名。
public function gatherMiddleware()
{
    if (! is_null($this->computedMiddleware)) {
        return $this->computedMiddleware;
    }

    $this->computedMiddleware = [];

    return $this->computedMiddleware = array_unique(array_merge(
        $this->middleware(), $this->controllerMiddleware()
    ), SORT_REGULAR);
}

路由定义的中间件是从 action 数组中取出来的:

 public function middleware($middleware = null)
{
    if (is_null($middleware)) {
        return (array) Arr::get($this->action, 'middleware', []);
    }

    if (is_string($middleware)) {
        $middleware = func_get_args();
    }

    $this->action['middleware'] = array_merge(
        (array) Arr::get($this->action, 'middleware', []), $middleware
    );

    return $this;
}

控制器定义的中间件:

public function controllerMiddleware()
{
    if (! $this->isControllerAction()) {
        return [];
    }

    return ControllerDispatcher::getMiddleware(
        $this->getController(), $this->getControllerMethod()
    );
}

public function getController()
{
    $class = $this->parseControllerCallback()[0];

    if (! $this->controller) {
        $this->controller = $this->container->make($class);
    }

    return $this->controller;
}

protected function getControllerMethod()
{
    return $this->parseControllerCallback()[1];
}

protected function parseControllerCallback()
{
    return Str::parseCallback($this->action['uses']);
}

public static function parseCallback($callback, $default = null)
{
    return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}

当前的路由如果使用控制器的时候,就要解析属性 use,解析出控制器的类名与类方法。接下来就需要 ControllerDispatcher 类。

在讲解 ControllerDispatcher 类之前,我们需要先了解一下控制器中间件:

abstract class Controller
{
    public function middleware($middleware, array $options = [])
    {
        foreach ((array) $middleware as $m) {
            $this->middleware[] = [
                'middleware' => $m,
                'options' => &$options,
            ];
        }

        return new ControllerMiddlewareOptions($options);
    }
}

class ControllerMiddlewareOptions
{
    protected $options;

    public function __construct(array &$options)
    {
        $this->options = &$options;
    }

    public function only($methods)
    {
        $this->options['only'] = is_array($methods) ? $methods : func_get_args();

        return $this;
    }

    public function except($methods)
    {
        $this->options['except'] = is_array($methods) ? $methods : func_get_args();

        return $this;
    }
}

在为控制器定义中间的是,可以为中间件利用 only 指定在当前控制器中调用该中间件的特定控制器方法,也可以利用 except指定在当前控制器禁止调用中间件的方法。这些信息都保存在控制器的变量 middlewareoptions 中。

在搜集控制器的中间件时,就要利用中间件的这些信息:

class ControllerDispatcher
{
    public static function getMiddleware($controller, $method)
    {
        if (! method_exists($controller, 'getMiddleware')) {
            return [];
        }

        return collect($controller->getMiddleware())->reject(function ($data) use ($method) {
            return static::methodExcludedByOptions($method, $data['options']);
        })->pluck('middleware')->all();
    }

    protected static function methodExcludedByOptions($method, array $options)
    {
        return (isset($options['only']) && ! in_array($method, (array) $options['only'])) ||
            (! empty($options['except']) && in_array($method, (array) $options['except']));
    }
}

ControllerDispatcher 类中,利用了 reject 函数对每一个中间件都进行了控制器方法的判断,排除了不支持该控制器方法的中间件。pluck 函数获取了控制器 $this->middleware[] 数组中 middleware 的所有元素。

中间件的解析

中间件解析主要的工作是将路由中中间件的别名转化为中间件全程,主要流程为:

class MiddlewareNameResolver
{
    public static function resolve($name, $map, $middlewareGroups)
    {
        if ($name instanceof Closure) {
            return $name;
        } elseif (isset($map[$name]) && $map[$name] instanceof Closure) {
            return $map[$name];

        } elseif (isset($middlewareGroups[$name])) {
            return static::parseMiddlewareGroup(
                $name, $map, $middlewareGroups
            );

        } else {
            list($name, $parameters) = array_pad(explode(':', $name, 2), 2, null);

            return (isset($map[$name]) ? $map[$name] : $name).
                   (! is_null($parameters) ? ':'.$parameters : '');
        }
    }
}

可以看出,解析的中间件对象有三种:闭包、中间件别名、中间件组。

  • 对于闭包来说,resolve 直接返回闭包;
  • 对于中间件别名来说,例如 auth ,会从 App\Http\Kernel 文件 $routeMiddleware 数组中寻找中间件全名 \Illuminate\Auth\Middleware\Authenticate::class
  • 对于具有参数的中间件别名来说,例如 throttle:60,1,会将别名转化为全名 \Illuminate\Routing\Middleware\ThrottleRequests::60,1
  • 对于中间件组来说,会调用 parseMiddlewareGroup 函数。
protected static function parseMiddlewareGroup($name, $map, $middlewareGroups)
{
    $results = [];

    foreach ($middlewareGroups[$name] as $middleware) {
        if (isset($middlewareGroups[$middleware])) {
            $results = array_merge($results, static::parseMiddlewareGroup(
                $middleware, $map, $middlewareGroups
            ));

            continue;
        }

        list($middleware, $parameters) = array_pad(
            explode(':', $middleware, 2), 2, null
        );

        if (isset($map[$middleware])) {
            $middleware = $map[$middleware];
        }

        $results[] = $middleware.($parameters ? ':'.$parameters : '');
    }

    return $results;
}

可以看出,对于中间件组来说,就要从 App\Http\Kernel 文件 $$middlewareGroups 数组中寻找组内的多个中间件,例如中间件组 api

'api' => [
    'throttle:60,1',
    'bindings',
]

解析出的中间件可能存在参数,别名转化为全名后函数返回。值得注意的是,中间件组内不一定都是别名,也有可能是中间件组的组名,例如:

'api' => [
    'throttle:60,1',
    'web',
]

'web' => [
    \App\Http\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
],

这时,就需要迭代解析。

中间件的排序

public function gatherRouteMiddleware(Route $route)
{
    $middleware = collect($route->gatherMiddleware())->map(function ($name) {
        return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
    })->flatten();

    return $this->sortMiddleware($middleware);
}

将所有中间件搜集并解析完毕后,接下来就要对中间件的调用顺序做一些调整,以确保中间件功能正常。

protected $middlewarePriority = [
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \Illuminate\Auth\Middleware\Authenticate::class,
    \Illuminate\Session\Middleware\AuthenticateSession::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];

数组 middlewarePriority 中保存着必须有一定顺序的中间件,例如 StartSession 中间件就必须运行在 ShareErrorsFromSession 之前。因此一旦路由中有这两个中间件,那么就要确保两者的顺序一致。

中间件的排序由函数 sortMiddleware 负责:

class SortedMiddleware extends Collection
{
    public function __construct(array $priorityMap, $middlewares)
    {
        if ($middlewares instanceof Collection) {
            $middlewares = $middlewares->all();
        }

        $this->items = $this->sortMiddleware($priorityMap, $middlewares);
    }

    protected function sortMiddleware($priorityMap, $middlewares)
    {
        $lastIndex = 0;

        foreach ($middlewares as $index => $middleware) {
            if (! is_string($middleware)) {
                continue;
            }

            $stripped = head(explode(':', $middleware));

            if (in_array($stripped, $priorityMap)) {
                $priorityIndex = array_search($stripped, $priorityMap);

                if (isset($lastPriorityIndex) && $priorityIndex < $lastPriorityIndex) {
                    return $this->sortMiddleware(
                        $priorityMap, array_values(
                            $this->moveMiddleware($middlewares, $index, $lastIndex)
                        )
                    );
                } else {
                    $lastIndex = $index;
                    $lastPriorityIndex = $priorityIndex;
                }
            }
        }

        return array_values(array_unique($middlewares, SORT_REGULAR));
    }

    protected function moveMiddleware($middlewares, $from, $to)
    {
        array_splice($middlewares, $to, 0, $middlewares[$from]);

        unset($middlewares[$from + 1]);

        return $middlewares;
    }
}

函数的方法很简单,检测当前中间件数组,查看是否存在中间件是数组 middlewarePriority 内元素。如果发现了两个中间件不符合顺序,那么就要调换中间件顺序,然后进行迭代。