前言

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

中间件的搜集

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

  1. public function dispatchToRoute(Request $request)
  2. {
  3. $route = $this->findRoute($request);
  4. $request->setRouteResolver(function () use ($route) {
  5. return $route;
  6. });
  7. $this->events->dispatch(new Events\RouteMatched($route, $request));
  8. $response = $this->runRouteWithinStack($route, $request);
  9. return $this->prepareResponse($request, $response);
  10. }
  11. protected function runRouteWithinStack(Route $route, Request $request)
  12. {
  13. $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
  14. $this->container->make('middleware.disable') === true;
  15. $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
  16. return (new Pipeline($this->container))
  17. ->send($request)
  18. ->through($middleware)
  19. ->then(function ($request) use ($route) {
  20. return $this->prepareResponse(
  21. $request, $route->run()
  22. );
  23. });
  24. }
  25. public function gatherRouteMiddleware(Route $route)
  26. {
  27. $middleware = collect($route->gatherMiddleware())->map(function ($name) {
  28. return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
  29. })->flatten();
  30. return $this->sortMiddleware($middleware);
  31. }

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

  • 在路由的定义过程中,利用关键字 middleware 为路由添加中间件,这种中间件都是在文件 App\Http\Kernel$middlewareGroups$routeMiddleware 这两个数组定义的中间件别名。
  • 在路由控制器的构造函数中,添加中间件,可以在这里定义一个闭包作为中间件,也可以利用中间件别名。
  1. public function gatherMiddleware()
  2. {
  3. if (! is_null($this->computedMiddleware)) {
  4. return $this->computedMiddleware;
  5. }
  6. $this->computedMiddleware = [];
  7. return $this->computedMiddleware = array_unique(array_merge(
  8. $this->middleware(), $this->controllerMiddleware()
  9. ), SORT_REGULAR);
  10. }

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

  1. public function middleware($middleware = null)
  2. {
  3. if (is_null($middleware)) {
  4. return (array) Arr::get($this->action, 'middleware', []);
  5. }
  6. if (is_string($middleware)) {
  7. $middleware = func_get_args();
  8. }
  9. $this->action['middleware'] = array_merge(
  10. (array) Arr::get($this->action, 'middleware', []), $middleware
  11. );
  12. return $this;
  13. }

控制器定义的中间件:

  1. public function controllerMiddleware()
  2. {
  3. if (! $this->isControllerAction()) {
  4. return [];
  5. }
  6. return ControllerDispatcher::getMiddleware(
  7. $this->getController(), $this->getControllerMethod()
  8. );
  9. }
  10. public function getController()
  11. {
  12. $class = $this->parseControllerCallback()[0];
  13. if (! $this->controller) {
  14. $this->controller = $this->container->make($class);
  15. }
  16. return $this->controller;
  17. }
  18. protected function getControllerMethod()
  19. {
  20. return $this->parseControllerCallback()[1];
  21. }
  22. protected function parseControllerCallback()
  23. {
  24. return Str::parseCallback($this->action['uses']);
  25. }
  26. public static function parseCallback($callback, $default = null)
  27. {
  28. return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
  29. }

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

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

  1. abstract class Controller
  2. {
  3. public function middleware($middleware, array $options = [])
  4. {
  5. foreach ((array) $middleware as $m) {
  6. $this->middleware[] = [
  7. 'middleware' => $m,
  8. 'options' => &$options,
  9. ];
  10. }
  11. return new ControllerMiddlewareOptions($options);
  12. }
  13. }
  14. class ControllerMiddlewareOptions
  15. {
  16. protected $options;
  17. public function __construct(array &$options)
  18. {
  19. $this->options = &$options;
  20. }
  21. public function only($methods)
  22. {
  23. $this->options['only'] = is_array($methods) ? $methods : func_get_args();
  24. return $this;
  25. }
  26. public function except($methods)
  27. {
  28. $this->options['except'] = is_array($methods) ? $methods : func_get_args();
  29. return $this;
  30. }
  31. }

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

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

  1. class ControllerDispatcher
  2. {
  3. public static function getMiddleware($controller, $method)
  4. {
  5. if (! method_exists($controller, 'getMiddleware')) {
  6. return [];
  7. }
  8. return collect($controller->getMiddleware())->reject(function ($data) use ($method) {
  9. return static::methodExcludedByOptions($method, $data['options']);
  10. })->pluck('middleware')->all();
  11. }
  12. protected static function methodExcludedByOptions($method, array $options)
  13. {
  14. return (isset($options['only']) && ! in_array($method, (array) $options['only'])) ||
  15. (! empty($options['except']) && in_array($method, (array) $options['except']));
  16. }
  17. }

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

中间件的解析

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

  1. class MiddlewareNameResolver
  2. {
  3. public static function resolve($name, $map, $middlewareGroups)
  4. {
  5. if ($name instanceof Closure) {
  6. return $name;
  7. } elseif (isset($map[$name]) && $map[$name] instanceof Closure) {
  8. return $map[$name];
  9. } elseif (isset($middlewareGroups[$name])) {
  10. return static::parseMiddlewareGroup(
  11. $name, $map, $middlewareGroups
  12. );
  13. } else {
  14. list($name, $parameters) = array_pad(explode(':', $name, 2), 2, null);
  15. return (isset($map[$name]) ? $map[$name] : $name).
  16. (! is_null($parameters) ? ':'.$parameters : '');
  17. }
  18. }
  19. }

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

  • 对于闭包来说,resolve 直接返回闭包;
  • 对于中间件别名来说,例如 auth ,会从 App\Http\Kernel 文件 $routeMiddleware 数组中寻找中间件全名 \Illuminate\Auth\Middleware\Authenticate::class
  • 对于具有参数的中间件别名来说,例如 throttle:60,1,会将别名转化为全名 \Illuminate\Routing\Middleware\ThrottleRequests::60,1
  • 对于中间件组来说,会调用 parseMiddlewareGroup 函数。
  1. protected static function parseMiddlewareGroup($name, $map, $middlewareGroups)
  2. {
  3. $results = [];
  4. foreach ($middlewareGroups[$name] as $middleware) {
  5. if (isset($middlewareGroups[$middleware])) {
  6. $results = array_merge($results, static::parseMiddlewareGroup(
  7. $middleware, $map, $middlewareGroups
  8. ));
  9. continue;
  10. }
  11. list($middleware, $parameters) = array_pad(
  12. explode(':', $middleware, 2), 2, null
  13. );
  14. if (isset($map[$middleware])) {
  15. $middleware = $map[$middleware];
  16. }
  17. $results[] = $middleware.($parameters ? ':'.$parameters : '');
  18. }
  19. return $results;
  20. }

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

  1. 'api' => [
  2. 'throttle:60,1',
  3. 'bindings',
  4. ]

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

  1. 'api' => [
  2. 'throttle:60,1',
  3. 'web',
  4. ]
  5. 'web' => [
  6. \App\Http\Middleware\EncryptCookies::class,
  7. \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
  8. ],

这时,就需要迭代解析。

中间件的排序

  1. public function gatherRouteMiddleware(Route $route)
  2. {
  3. $middleware = collect($route->gatherMiddleware())->map(function ($name) {
  4. return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
  5. })->flatten();
  6. return $this->sortMiddleware($middleware);
  7. }

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

  1. protected $middlewarePriority = [
  2. \Illuminate\Session\Middleware\StartSession::class,
  3. \Illuminate\View\Middleware\ShareErrorsFromSession::class,
  4. \Illuminate\Auth\Middleware\Authenticate::class,
  5. \Illuminate\Session\Middleware\AuthenticateSession::class,
  6. \Illuminate\Routing\Middleware\SubstituteBindings::class,
  7. \Illuminate\Auth\Middleware\Authorize::class,
  8. ];

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

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

  1. class SortedMiddleware extends Collection
  2. {
  3. public function __construct(array $priorityMap, $middlewares)
  4. {
  5. if ($middlewares instanceof Collection) {
  6. $middlewares = $middlewares->all();
  7. }
  8. $this->items = $this->sortMiddleware($priorityMap, $middlewares);
  9. }
  10. protected function sortMiddleware($priorityMap, $middlewares)
  11. {
  12. $lastIndex = 0;
  13. foreach ($middlewares as $index => $middleware) {
  14. if (! is_string($middleware)) {
  15. continue;
  16. }
  17. $stripped = head(explode(':', $middleware));
  18. if (in_array($stripped, $priorityMap)) {
  19. $priorityIndex = array_search($stripped, $priorityMap);
  20. if (isset($lastPriorityIndex) && $priorityIndex < $lastPriorityIndex) {
  21. return $this->sortMiddleware(
  22. $priorityMap, array_values(
  23. $this->moveMiddleware($middlewares, $index, $lastIndex)
  24. )
  25. );
  26. } else {
  27. $lastIndex = $index;
  28. $lastPriorityIndex = $priorityIndex;
  29. }
  30. }
  31. }
  32. return array_values(array_unique($middlewares, SORT_REGULAR));
  33. }
  34. protected function moveMiddleware($middlewares, $from, $to)
  35. {
  36. array_splice($middlewares, $to, 0, $middlewares[$from]);
  37. unset($middlewares[$from + 1]);
  38. return $middlewares;
  39. }
  40. }

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