生命周期 11-江南app体育官方入口

在dingo api视频中老师有一句:“dingo接管了api路由”,让我思索了许久。。今天终于想清楚了。。

要明白这句话,首先得对laravel生命周期有一定的理解。

如果不熟悉生命周期,建议阅读我小结的如下系列文章(并点赞 :smile:)。

  • 生命周期1 app对象解析
  • 生命周期2
  • 生命周期3
  • 生命周期4
  • 生命周期5 管道流分析
  • 生命周期6
  • 生命周期7
  • 生命周期8
  • 生命周期9
  • 生命周期10

我们知道,访问laravel应用程序有两种方式:

  • 通过浏览器地址栏(普通web路由)
  • 通过api接口(api路由)

dingo接管的路由仅仅是api路由,并非全部路由。

下面简要分析一下这两种方式的差别,从而看清楚"dingo是如何接管了api的路由"的。

先通过一个表格来对比。

特性 通过浏览器 通过api
访问url (user-token中id=1)
路由定义所在文件 routes/web.php(对user使用了资源路由) routes/api.php(路由直接手写定义)
引入composer自动加载类 引入composerloader类 相同
创建app对象 创建服务容器,并设置全局路径,绑定基础类,注册基本服务,设置核心类的别名 相同
创建apphttpkernel对象 通过服务容器,创建apphttpkernel对象及其依赖的其他对象 相同
创建request对象 以symfonyrequest对象为模板创建一个illuminaterequest对象 相同
apphttpkernel的bootstrap流程 载入环境变量,读取配置文件,设置异常处理,引入aliasloader类,设置facade别名,注册各种服务,启动各种服务 相同
第一个管道流 illuminaterequest对象按正常顺序通过全局中间件过滤后,到达illuminaterouter路由器 有差异
第二个管道流前 中间件:从illuminate路由上绑定的中间件(来自app/http/kernel.php中的web中间件组)和从控制器上设置的中间件。 有差异
第二个管道流 穿过上面的路由&控制器中间件 有差异
与应用程序交互 执行控制器方法,返回待处理结果 类似
准备response对象 根据返回数据类型,自动选择处理逻辑 类似
返回response对象--第二个管道流 进行后置中间件的一些处理后返回 类似
返回response对象--两个管道流之间 没有处理 有差异
返回response对象--第一个管道流 中间件没有后置处理代码,直接返回 相同

第一个管道流

在第一个全局中间件\dingo\api\http\middleware\request的handle方法中,就被接管了。

    public function handle($request, closure $next)
    {
        try {
            if ($this->validator->validaterequest($request)) {
                $this->app->singleton(laravelexceptionhandler::class, function ($app) {
                    return $app[exceptionhandler::class];
                });
                $request = $this->app->make(requestcontract::class)->createfromilluminate($request);
                $this->events->fire(new requestwasmatched($request, $this->app));
                return $this->sendrequestthroughrouter($request);
            }
        } catch (exception $exception) {
            $this->exception->report($exception);
            return $this->exception->handle($exception);
        }
        return $next($request);
    }

if ($this->validator->validaterequest($request)) {

这句就进行了判断,如果是api接口调用,那么就会转到dingo上去执行了,所有的一切变化从此开始。

可以看到,根据illuminaterequest对象创建了dingorequest对象,并代替illuminaterequest对象,进入新的管道流过滤。

新的第一个管道流:

    protected function sendrequestthroughrouter(httprequest $request)
    {
        $this->app->instance('request', $request);
        return (new pipeline($this->app))->send($request)->through($this->middleware)->then(function ($request) {
            return $this->router->dispatch($request);
        });
    }

此时通过的中间件是在\dingo\api\provider\laravelserviceprovider的boot方法中读取apphttpkernel的全局中间件数组,并绑定到\dingo\api\http\middleware\request对象中,然后被读取到的。

vendor/dingo/api/src/provider/laravelserviceprovider.php:22

public function boot()
    {
        ...
        $kernel = $this->app->make(kernel::class);
        $this->app[request::class]->mergemiddlewares(
            $this->gatherappmiddleware($kernel)
        );

此时创建了\dingo\api\http\middleware\request对象,并调用其mergemiddlewares将apphttpkernel的全局中间件数组提取处理附加到自己身上。

第二个管道流前

\dingo\api\http\middleware\request::sendrequestthroughrouter,

return $this->router->dispatch($request);

\dingo\api\routing\router::dispatch中:

    public function dispatch(request $request)
    {
        ...
        try {
            $response = $this->adapter->dispatch($request, $request->version());
        } catch (exception $exception) {
            ...
        }
        return $this->prepareresponse($response, $request, $request->format());
    }

\dingo\api\routing\adapter\laravel::dispatch中:

    public function dispatch(request $request, $version)
    {
        if (! isset($this->routes[$version])) {
            throw new unknownversionexception;
        }
        $routes = $this->mergeoldroutes($version);
        $this->router->setroutes($routes);
        $router = clone $this->router;
        $response = $router->dispatch($request);
        unset($router);
        return $response;
    }

路由器已经变成了dingorouter,request变成了dingorequest对象。

在路由器上还增加了一个适配器(adapter),通过适配器来发送request到路由。路由还有了版本的区别。

将illuminaterouter中的所有路由和api的路由进行合并得到新的路由集合。并将此集合赋给illuminaterouter。

克隆一个新的illuminaterouter,然后由它去发送dingorequest对象,从而进入了第二个管道流。也就是说第二个管道流是通过illuminaterouter来完成的。

第二个管道流

找到request的匹配路由都跟第一种方式一致。

但在收集中间件时有明细差异,原因在于此时路由的中间件没有通过web中间件组,而是通过了api中间件组(且api中间件组非app/http/kernel.php中的,而是定义在routes/api.php中的)。

代码见\illuminate\routing\router::runroutewithinstack

...
$middleware = $shouldskipmiddleware ? [] : $this->gatherroutemiddleware($route);
...

这里的$this->gatherroutemiddleware($route)是通过此路由去找附在其上的中间件。

\illuminate\routing\router::gatherroutemiddleware

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

\illuminate\routing\route::gathermiddleware

    public function gathermiddleware()
    {
        ...
        return $this->computedmiddleware = array_unique(array_merge(
            $this->middleware(), $this->controllermiddleware()
        ), sort_regular);
    }

关键在于$this->middleware()是什么?

\illuminate\routing\route::middleware

    public function middleware($middleware = null)
    {
        if (is_null($middleware)) {
            return (array) ($this->action['middleware'] ?? []);
        }
        ...
        return $this;
    }

由于传入的参数$middleware为null,因此,会返回$this->action['middleware'],也就是说当前匹配路由的action属性中的middlware键值,此值对最后收集到的中间件有巨大影响。

下面分两种情况具体说明一下。

浏览器调用

如果当前url是""。

那么这个值是在调用\app\providers\routeserviceprovider的boot方法时,调用\app\providers\routeserviceprovider::mapwebroutes,最终调用了\illuminate\routing\router::createroute附加上去的。

...
$route = $this->newroute(
    $methods, $this->prefix($uri), $action
);
//由于此url属于web中间件组
if ($this->hasgroupstack()) {
    $this->mergegroupattributesintoroute($route);
}

因此,$this->action['middleware']web,而通过middlewarenameresolver::resolve,就会将web转换为数个全局中间件的类全名。这样,就得到了一个全类名中间件集合,用于第二个管道流过滤,内容为:

  array (
    0 => 'app\\http\\middleware\\encryptcookies',
    1 => 'illuminate\\routing\\middleware\\substitutebindings',
    2 => 'illuminate\\cookie\\middleware\\addqueuedcookiestoresponse',
    3 => 'illuminate\\session\\middleware\\startsession',
    4 => 'illuminate\\view\\middleware\\shareerrorsfromsession',
    5 => 'app\\http\\middleware\\verifycsrftoken',
    6 => 'app\\http\\middleware\\recordlastactivedtime',
    7 => 'app\\http\\middleware\\redirectifauthenticated',
  )

api接口调用

如果当前url是"。

那么这个值是在调用\app\providers\routeserviceprovider的boot方法时,调用\app\providers\routeserviceprovider::mapapiroutes,然后在定义$api->get('user', 'userscontroller@me')时,调用了\dingo\api\routing\router::addroute,然后调用\dingo\api\routing\adapter\laravel::addroute附加上去的。

    public function addroute($methods, $uri, $action)
    {
        ...
        //获得routes/api.php中定义的嵌套group路由的中间件
        $action = $this->mergelastgroupattributes($action);
        //添加一个preparation controller中间件
        $action = $this->addcontrollermiddlewaretorouteaction($action);
        ...
        return $this->adapter->addroute((array) $methods, $action['version'], $uri, $action);
    }
    public function addroute(array $methods, array $versions, $uri, $action)
    {
        $this->createroutecollections($versions);
        $route = new route($methods, $uri, $action);
        $route->where($action['where']);
        foreach ($versions as $version) {
            $this->routes[$version]->add($route);
        }
        return $route;
    }

这样,也得到了一个全类名中间件集合,用于第二个管道流过滤,内容为:

    ‌array (
        0 => 'dingo\\api\\http\\middleware\\preparecontroller',
        1 => 'liyu\\dingo\\serializerswitch:array',
        2 => 'illuminate\\routing\\middleware\\substitutebindings',
        3 => 'dingo\\api\\http\\middleware\\ratelimit',
        4 => 'dingo\\api\\http\\middleware\\auth',
    )

结论

由此可见,路由的中间件都是在定义路由的时候就已经绑定好了。

返回对象

返回对象在第一个管道流和第二管道流之间时,会通过 dingorouter 对返回的dingoresponse对象,dingorequest对象进行处理。
\dingo\api\routing\router::dispatch

    public function dispatch(request $request)
    {
        ...
        try {
            $response = $this->adapter->dispatch($request, $request->version());
        } catch (exception $exception) {
            ...
        }
        return $this->prepareresponse($response, $request, $request->format());
    }

\dingo\api\routing\router::prepareresponse

    protected function prepareresponse($response, request $request, $format)
    {
        if ($response instanceof illuminateresponse) {
            $response = response::makefromexisting($response);
        } elseif ($response instanceof jsonresponse) {
            $response = response::makefromjson($response);
        }
        if ($response instanceof response) {
            // if we try and get a formatter that does not exist we'll let the exception
            // handler deal with it. at worst we'll get a generic json response that
            // a consumer can hopefully deal with. ideally they won't be using
            // an unsupported format.
            try {
                $response->getformatter($format)->setresponse($response)->setrequest($request);
            } catch (notacceptablehttpexception $exception) {
                return $this->exception->handle($exception);
            }
            $response = $response->morph($format);
        }
        if ($response->issuccessful() && $this->requestisconditional()) {
            if (! $response->headers->has('etag')) {
                $response->setetag(sha1($response->getcontent()));
            }
            $response->isnotmodified($request);
        }
        return $response;
    }

注意: $response = $response->morph($format); 如果在控制器中使用了transformer,那么会在这个地方进行处理,从而可以控制response的输出格式。

  • vendor/laravel/framework/src/illuminate/routing/router.php:471
  • vendor/barryvdh/laravel-debugbar/src/serviceprovider.php:103
  • vendor/dingo/api/src/provider/laravelserviceprovider.php:30
  • vendor/dingo/api/src/provider/laravelserviceprovider.php:34
  • vendor/dingo/api/src/http/middleware/request.php:91
  • vendor/dingo/api/src/routing/router.php:503
  • vendor/dingo/api/src/routing/adapter/laravel.php:69

从上面分析,我们可以得知dingo主要是通过自已的路由器,改变了request的走向,从而影响了整个程序的运行过程。

本作品采用《cc 协议》,转载必须注明作者和本文链接
日拱一卒
本帖由系统于 5年前 自动加精
以构建论坛项目 larabbs 为线索,展开对 laravel 框架的全面学习。应用程序架构思路贴近 laravel 框架的设计哲学。
从零开始带你一步步开发一个 go 博客项目,让你在最短的时间内学会使用 go 进行编码。项目结构很大程度上参考了 laravel。
讨论数量: 11

我觉得讲的有点问题

if ($this->validator->validaterequest($request)) 

这句话 我加了断点,非api的端点也进入了,我理解的是,dingo接管了所有的路由

置于再何时分叉,还要继续加断点

6年前

这个我怎么说呢?你运行的url是什么,点开源码看看就明白了。生命周期走一遍,不同的方式都走一遍你就明白了。

6年前

****一会debug看看, 谢谢楼主的分享!

6年前

@hustnzj
我运行的url是 /
然后 $this->validator->validaterequest($request) 发现可以通过的

6年前

/ 我这里是直接不通过的。你配置文件里是怎么配的?

验证代码都在\dingo\api\http\requestvalidator::validaterequest

    public function validaterequest(illuminaterequest $request)
    {
        $passed = false;
        foreach ($this->validators as $validator) {
            $validator = $this->container->make($validator);
            if ($validator instanceof validator && $validator->validate($request)) {
                $passed = true;
            }
        }
        // the accept validator will always be run once any of the previous validators have
        // been run. this ensures that we only run the accept validator once we know we
        // have a request that is targeting the api.
        if ($passed) {
            $this->container->make(accept::class)->validate($request);
        }
        return $passed;
    }

你可以再debug看看是哪里出了问题?

6年前

我是5.7 版本的,我这边确实是通过的,而且不一定是出了问题

6年前

我也是5.7版本,看看自己的配置文件吧,不一定出了问题?那你debug过程写出来?
环境配置,一个变量都会有影响,差别很大,你要不debug,如何证明不一定出了问题?

6年前

@hustnzj 大神,我就是debug的,否则我怎么知道走了那个函数呢?

6年前

过程和配置文件。。

6年前

@hustnzj
配置文件真不太方便,我回头找一下原因,再发帖通知你

6年前

幸好老师这里刚说明了,供参考:

估计你还是没有点进去看如何验证的。你要是把验证代码看完了,就知道我说的配置文件的哪些内容了。验证就是两部分,域名和前缀。

6年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
网站地图