路由调度之路由对象的由来

简介

历经 5 章的漫长旅程,我们终于把 Laravel 全局中间件的运行原理看了一遍,相信看过的童鞋,一定会有所收获。

或许,中间存在一些不足和误解之处,现在暂且没有发现,希望以后的深入学习中,能够慢慢领悟,并修正错误。

好了,废话不多讲了,现在我们正式进入 Laravel Router 路由调度的学习中。

Pipeline 管道结尾(即:洋葱芯的运行原理)

Illuminate\Pipeline\Pipeline

public function then(Closure $destination)
{
    // $this->prepareDestination($destination) 就是管道操作的最终一步
    $pipeline = array_reduce(
        array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );

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

我们看一下 $this->prepareDestination($destination) 的调用

Illuminate\Pipeline\Pipeline

protected function prepareDestination(Closure $destination)
{
    // 标准管道操作的闭包函数,这个闭包函数会在最后一个全局中间件 `TrustProxies` 中通过 $next($request) 调用
    return function ($passable) use ($destination) {
        return $destination($passable);
    };
}

我们看 $destination($passable),其中 $destination 为闭包函数,那么它是从哪里传过来的呢。

答案看下面

Illuminate\Foundation\Http\Kernel

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());
}

看到最后一行 then($this->dispatchToRouter()) 的调用啦吗,其中 $this->dispatchToRouter() 的返回值就是上面 $destination

那么我们来看 $this->dispatchToRouter()

Illuminate\Foundation\Http\Kernel

protected function dispatchToRouter()
{
    return function ($request) {
        // 将过滤好的 $request 实例,绑定到 Laravel 容器中
        $this->app->instance('request', $request);

        // 核心!!!! 路由调度,即路由匹配与运行的正式开始代码
        return $this->router->dispatch($request);
    };
}

return $this->router->dispatch($request); 简单的一句话,包含了 路由调度、控制器与方法匹配、响应生成等等 Laravel 核心操作,包括我们写的控制器代码、模型代码都在这一句里面被执行。

$this->router 怎么来的。

这个就要从 Laravel 中的门面、依赖注入、容器初始化来说了。

我们先看一下 Illuminate\Foundation\Http\Kernel 类的构造方法

public function __construct(Application $app, Router $router)
{
    $this->app = $app;

    // 路由就是在这里被赋值的。
    $this->router = $router;

    $router->middlewarePriority = $this->middlewarePriority;

    foreach ($this->middlewareGroups as $key => $middleware) {
        $router->middlewareGroup($key, $middleware);
    }

    foreach ($this->routeMiddleware as $key => $middleware) {
        $router->aliasMiddleware($key, $middleware);
    }
}

但是你肯定要问,Illuminate\Foundation\Http\Kernel 什么时候被实例化的呀?

这个最开始的章节我有简单提过,现在再回顾一下

  • 我们来看 public/index.php

    <?php
    
    // ...
    
    // 这个地方就是 `Illuminate\Foundation\Http\Kernel` 类被实例化的地方
    $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
    
    $response = $kernel->handle(
      $request = Illuminate\Http\Request::capture()
    );
    
    // ...
  • 关于 Laravel 实例化类的核心方法: make

    前面我有提过,Laravel 管理服务实例的方法就是通过 bindings 属性进行操作的。bindings 属性就是一个一维关联数组

    实际调用 make 方法只传 abstract 参数,abstract 可能是一个单词也可能是一个类名(包含命名空间)

    例如:

    $app->make('router');
    // 或者
    $app->make(Illuminate\Contracts\Http\Kernel::class);

    下面我具体来说,假设 bindings 属性可以如下进行抽象操作

    protected $bindings = [
      // ...
      $abstract => $concrete,
      // ...
    ];
    • $abstract 一定是字符串类型,可能是一个单词,也可能是一个类名(包含命名空间);
    • $concrete 一定是闭包函数,

    如下几种情况:

    • 第一种,$bindings 里没有以 $abstract 这个键,那么此时 $abstract 一定是一个类名,最终结果就是实例化 $abstract 这个类,同时以 $abstract 类名为键,把 $abstract 生成一个能够返回对象的闭包,存入 $bindings 里面,方便后面取用

    • 第二种,$bindings 里有以 $abstract 这个键,$abstract 此时可以是一个单词也可以是一个类名,那么 $concrete 是类名时,$concrete 将生成一个能够返回相关对象的闭包,$abstract 不会被实例化,而后赋值给 $bindings,方便后面取用

    • 第三种,$bindings 里有以 $abstract 这个键,且 $concrete 是一个闭包,那么 $concrete 将被调用,必须返回一个对象,最后 $bindings 移除 $abstract 这个键值对,赋值被 instances 属性

    现在你肯定在想了:类的实例化。如果类有构造函数,那么肯定会调用构造函数呀。如果构造函数参数上有类型约束,比如上面的 Router $router 又该如何调用呀。

    这个就用到了 PHP 的反射类操作了,简单点讲反射类能够得到一个类构造函数参数所需的类型约束的全类名,既然得到了全类名,当然继续 make ,直到所有 类名 都被实例化,最开始的 $app->make($abstract) 才能算是完成。

    上面 Illuminate\Foundation\Http\Kernel 就属于第二种情况。而 Router $router 就会从 bindings 中去以 'router' 为键的类对象。你又想了,怎么成了 'router' 单词了呢。

  • 关于为什么 Router 约束不以 Illuminate\Routing\Router 为键名,而已 router 为键名的原因

    记得 Application 的初始化吗

    $this->registerCoreContainerAliases();

    这个就是原因:

    public function registerCoreContainerAliases()
    {
      foreach ([
          'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
          'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
          'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
          'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
          'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
          'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
          'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
          'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
          'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
          'db'                   => [\Illuminate\Database\DatabaseManager::class],
          'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
          'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
          'files'                => [\Illuminate\Filesystem\Filesystem::class],
          'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
          'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
          'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
          'hash'                 => [\Illuminate\Hashing\HashManager::class],
          'hash.driver'          => [\Illuminate\Contracts\Hashing\Hasher::class],
          'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
          'log'                  => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
          'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
          'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
          'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
          'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
          'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
          'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
          'redirect'             => [\Illuminate\Routing\Redirector::class],
          'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
          'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
          'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
          'session'              => [\Illuminate\Session\SessionManager::class],
          'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
          'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
          'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
          'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
      ] as $key => $aliases) {
          foreach ($aliases as $alias) {
              $this->alias($key, $alias);
          }
      }
    }

    我们找到 router,可到它的值里面是不是有个 \Illuminate\Routing\Router::class

    make 方法在实际操作中,如果 $abstract 是个类名,且在上面定义数组中存在,优先取其所指向的单词作为 make 的参数。

    router 在 Application 初始化时就已经被绑定了,其绑定的就是返回 Illuminate\Routing\Router 类实例的闭包函数。

PS: 啰嗦了一卡车,感觉差不多弄明白了 $this->router 来源

Illuminate\Routing\Router 类中的路由调度方法 dispatch

public function dispatch(Request $request)
{
    // 设置 currentRequest 属性为 $request
    $this->currentRequest = $request;

    // 调用 dispatchToRoute 方法正式进行路由调度
    return $this->dispatchToRoute($request);
}
public function dispatchToRoute(Request $request)
{
    // 通过字面意思不难理解,先找路由(即路由匹配),然后运行路由
    return $this->runRoute($request, $this->findRoute($request));
}

小节结尾语

这一章,主要对前面内容进行了简单回顾,下一节在进行 findRoute 方法讲解前,我们先屡清楚下面这 3 个类的关系

Illuminate\Routing\Router
Illuminate\Routing\RouteCollection
Illuminate\Routing\Route

一句话 Illuminate\Routing\RouteCollectionIlluminate\Routing\Route 集合,Illuminate\Routing\Router 负责集合内容的代理管理,包括添加路由,路由调度,路由运行等等。

即:

Illuminate\Routing\Router 是执行者,例如我们人
Illuminate\Routing\RouteCollection 是容器,例如我们面前盒子
Illuminate\Routing\Route 是元素,例如我们面前盒子里面的物品

这有点像资源库模式。

本篇如有错误、不当或者需补充的内容,请各位同僚多提宝贵意见。

本文章首发在 LearnKu.com 网站上。
上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~