【Laravel-海贼王系列】第十三章,路由&控制器解析

栏目: PHP · 发布时间: 5年前

内容简介:这块代码是在展开完整的服务提供者后面在使用中会涉及这里注册的对象,红框内就是注册的绑定关系。

这块代码是在 Application 的构造函数中加载的

public function __construct($basePath = null)
{
    ...
    $this->registerBaseServiceProviders();
    ...
}
复制代码
protected function registerBaseServiceProviders()
{
    ...
    $this->register(new RoutingServiceProvider($this));
    ...
}
复制代码

展开完整的服务提供者

<?php

namespace Illuminate\Routing;

use Illuminate\Support\ServiceProvider;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response as PsrResponse;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Illuminate\Contracts\View\Factory as ViewFactoryContract;
use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract;
use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract;

class RoutingServiceProvider extends ServiceProvider
{

    public function register()
    {
        $this->registerRouter();
        $this->registerUrlGenerator();
        $this->registerRedirector();
        $this->registerPsrRequest();
        $this->registerPsrResponse();
        $this->registerResponseFactory();
        $this->registerControllerDispatcher();
    }

    protected function registerRouter()
    {
        $this->app->singleton('router', function ($app) {
            return new Router($app['events'], $app);
        });
    }

    protected function registerUrlGenerator()
    {
        $this->app->singleton('url', function ($app) {
            $routes = $app['router']->getRoutes();

            $app->instance('routes', $routes);

            $url = new UrlGenerator(
                $routes, $app->rebinding(
                    'request', $this->requestRebinder()
                ), $app['config']['app.asset_url']
            );

            $url->setSessionResolver(function () {
                return $this->app['session'];
            });

            $url->setKeyResolver(function () {
                return $this->app->make('config')->get('app.key');
            });

            $app->rebinding('routes', function ($app, $routes) {
                $app['url']->setRoutes($routes);
            });

            return $url;
        });
    }

    protected function requestRebinder()
    {
        return function ($app, $request) {
            $app['url']->setRequest($request);
        };
    }

    protected function registerRedirector()
    {
        $this->app->singleton('redirect', function ($app) {
            $redirector = new Redirector($app['url']);

            if (isset($app['session.store'])) {
                $redirector->setSession($app['session.store']);
            }

            return $redirector;
        });
    }

    protected function registerPsrRequest()
    {
        $this->app->bind(ServerRequestInterface::class, function ($app) {
            return (new DiactorosFactory)->createRequest($app->make('request'));
        });
    }

    protected function registerPsrResponse()
    {
        $this->app->bind(ResponseInterface::class, function () {
            return new PsrResponse;
        });
    }

    protected function registerResponseFactory()
    {
        $this->app->singleton(ResponseFactoryContract::class, function ($app) {
            return new ResponseFactory($app[ViewFactoryContract::class], $app['redirect']);
        });
    }

    protected function registerControllerDispatcher()
    {
        $this->app->singleton(ControllerDispatcherContract::class, function ($app) {
            return new ControllerDispatcher($app);
        });
    }
}

复制代码

后面在使用中会涉及这里注册的对象,红框内就是注册的绑定关系。

【Laravel-海贼王系列】第十三章,路由&控制器解析

启动在这里并没有完成,这仅仅是启动系统的基础路由,在 app.php 中还有一个路由服务提供者 RouteServiceProvider

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{
    protected $namespace = 'App\Http\Controllers';

    public function boot()
    {
        // boot() 方法是在服务提供者所有 register() 方法执行完成之后在统一执行的
        
        // 这段代码最后会调用 $this->map();
        parent::boot();
    }

    public function map()
    {
        $this->mapApiRoutes();

        $this->mapWebRoutes();

    }

    // 这一块的逻辑非常复杂就不展开了,主要功能就是优先加载 cache/routes.php,如果不存在
    // 则从给定的路径加载路由文件
   
    protected function mapWebRoutes()
    {
        Route::middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php')); 
    }

    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }
}

复制代码

内核启动

注册完成之后就是开始处理,是从内核的 handle() 方法开始处理请求

protected function sendRequestThroughRouter($request)
    {
        ...

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }
复制代码

这段代码在 【Laravel-海贼王系列】第七章,Pipeline 类解析 解析过了 不了解执行逻辑请先看上一篇哦~

这里会在运行完中间件之后最后运行 $this->dispatchToRouter() 这个方法。

$this->router 对象是在内核的构造函数注入的 \Illuminate\Routing\Router 对象

protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);
            
            return $this->router->dispatch($request);
        };
    }
复制代码

那么我们接着看 dispatch 方法

public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

        return $this->dispatchToRoute($request);
    }
复制代码

转发一个请求给路由返回一个响应对象

public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }
复制代码

找到路由

我的理解: router 代表路由器, route 则是代表一次路由的对象,

所有路由器的功能就是执行,派发路由对象。所以我们需要先通过请求来拿到一个路由对象

protected function findRoute($request)
    {
        $this->current = $route = $this->routes->match($request);

        // 绑定最新的 $route 对象到容器
        
        $this->container->instance(Route::class, $route);
        
        // 返回路由
        return $route;
    }
复制代码

继续分析 $this->routes->match($request); ,

这里的 $this->routes 是构造函数注入的 Illuminate\Routing\RouteCollection 对象

public function match(Request $request)
    {

        $routes = $this->get($request->getMethod()); 

        $route = $this->matchAgainstRoutes($routes, $request);

        if (! is_null($route)) {
            return $route->bind($request);
        }

        $others = $this->checkForAlternateVerbs($request);

        if (count($others) > 0) {
            return $this->getRouteForMethods($request, $others);
        }

        throw new NotFoundHttpException;
    }
复制代码

$routes 对象这里面的值来自与路由缓存文件或者路由文件解析结果

【Laravel-海贼王系列】第十三章,路由&控制器解析

继续看 $route = $this->matchAgainstRoutes($routes, $request); 执行结果从请求中匹配对应路由并返回

【Laravel-海贼王系列】第十三章,路由&控制器解析

如果没有匹配的路由则使用请求方法以外的方法继续匹配

public static $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];

protected function checkForAlternateVerbs($request)
    {
        $methods = array_diff(Router::$verbs, [$request->getMethod()]);

        $others = [];

        foreach ($methods as $method) {
            if (! is_null($this->matchAgainstRoutes($this->get($method), $request, false))) {
                $others[] = $method;
            }
        }

        return $others;
    }
复制代码

执行完成返回 $other 数组,如果还是没有则抛出 throw new NotFoundHttpException;

这里不详细叙述了,如果匹配成功我们将得到一个 Illuminate\Routing\Route 对象传递下去。

派发路由

当我们得到路由对象之后就是派发它了,根据给定的路由返回响应对象

protected function runRoute(Request $request, Route $route)
    {
        // 将这个闭包设置到 request 对象的 $this->routeResolver 成员上
        
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });

        // 执行路由匹配的事件,框架刚启动的时候这里什么都不做
        $this->events->dispatch(new Events\RouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
    }
复制代码

获取响应

执行到这里就已经到了最后的部分了

return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
复制代码

这个方法就是将 $request$response 根据里面的属性封装好数据返回而已。

public function prepareResponse($request, $response)
    {
        return static::toResponse($request, $response);
    }
复制代码

重点看 $this->runRouteWithinStack($route, $request) 这段话才是将请求传递到控制器关键!

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()
                            );
                        });
    }
复制代码

又到了这种用法,不理解执行逻辑请看第七章,

根据 Pipeline 的使用远离我们最后在通过所有 $middleware

之后会将 $requeset 传递给闭包来结束

所以这是终点!

function ($request) use ($route) {
                            return $this->prepareResponse(
                                $request, $route->run()
                            );
                        }
复制代码

刚才说过了 $this-prepareResponse() 这个方法没什么亮点就是

将请求和响应对象封装返回,所有我们应该知道了,$route->run() 将返回 response 对象!

控制器闪亮登场

来吧,经历了无数令人发指的封装希望后面一片坦途, run()

public function run()
    {
        $this->container = $this->container ?: new Container;

        try {
        
            if ($this->isControllerAction()) { 
                return $this->runController();
            }

            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }
复制代码

总算看到了 runController() 方法了,想必路由跨过山和大海最总的归宿也到这儿了

$this-isControllerAction() 是判断路由是闭包还是字符串

【Laravel-海贼王系列】第十三章,路由&控制器解析

如果是字符串向上图红框中的内容则执行

protected function runController()
    {
        return $this->controllerDispatcher()->dispatch(
            $this, $this->getController(), $this->getControllerMethod()
        );
    }
复制代码

这里是调用 Illuminate\Routing\ControllerDispatcherdispatch 方法

public function dispatch(Route $route, $controller, $method)
    {
        $parameters = $this->resolveClassMethodDependencies(
            $route->parametersWithoutNulls(), $controller, $method
        ); // 从容器获取当前类构造函数依赖和方法依赖参数

        if (method_exists($controller, 'callAction')) {
            return $controller->callAction($method, $parameters);
        }

        return $controller->{$method}(...array_values($parameters));
    }
复制代码

callAction 来自所有控制器基础的 Illuminate\Routing\Controller

public function callAction($method, $parameters)
    {
        return call_user_func_array([$this, $method], $parameters);
    }
复制代码

没什么好讲的其实就是调用控制器对应的方法。

如果路由是闭包形式,则直接抽取路由对象中的闭包进行调用

protected function runCallable()
    {
        $callable = $this->action['uses'];

        // 通过容器抽取依赖的参数传入闭包运行
        return $callable(...array_values($this->resolveMethodDependencies(
            $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
        )));
    }
复制代码

结语

总算结束了, Laravel 路由在启动阶段注册了非常多的类,

1. Application 构造阶段 $this->register(new RoutingServiceProvider($this));

2. Kernel handle() bootstrap() 阶段加载服务提供者的时候包含了 App\Providers\RouteServiceProvider::class,

这两个阶段注册加上加载的逻辑是非常复杂,但是目的也很简单从就是从路由文件转成路由对象的过程,没有力气分析进去。

其他的就是最后一直调用到控制器的过程,其中最后的 resolveClassMethodDependenciesresolveMethodDependencies

也是非常值得研究的代码。


以上所述就是小编给大家介绍的《【Laravel-海贼王系列】第十三章,路由&控制器解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C程序设计

C程序设计

谭浩强 / 清华大学出版社 / 2005-7-1 / 26.00元

《C程序设计(第3版)》作者具有丰富的教学经验和编写教材的经验,并针对初学者的特点,精心策划、准确定位,使得《C程序设计(第3版)》概念清晰、例题丰富、深入浅出,受到专家和读者的一致好评。《C程序设计(第3版)》被普遍认为是学习C语言的好教材,并被全国大多数高校选用。十多年来《C程序设计(第3版)》累计发行了800多万册,刨同类书的全国最高记录,是学习C语言的主流用书。《C程序设计(第3版)》曾荣......一起来看看 《C程序设计》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具