• Laravel Reponse 响应客户端


    Laravel Response 响应客户端

    本篇文章逻辑较长,只说明和响应生命周期相关的必要代码。

    本文主要内容顺序为:

    1、执行上文管道中的then方法指定的闭包,路由的分发

    2、在路由器中(Router类)找到请求($request 也就是经过全局中间件处理的请求)匹配的路由规则

    3、说明路由规则的加载(会跳转到框架的boot过程),注意这部分是在处理请求之前完成的,因为一旦当我们开始处理请求,就意味着所有的路由都应该已经加载好了,供我们的请求进行匹配

    4、执行请求匹配到的路由逻辑

    5、生成响应,并发送给客户端

    6、最后生命周期的结束

    7、基本响应类的使用

    前文说道,如果一个请求顺利通过了全局中间件那么就会调用管道then方法中传入的闭包

    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());
    }
    
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);
            return $this->router->dispatch($request);
        };
    }
    

    查看IlluminateRoutingRouter::dispatch方法

    public function dispatch(Request $request)
    {   
        $this->currentRequest = $request;
    	// 将请求分发到路由
        // 跳转到dispatchToRoute方法
        return $this->dispatchToRoute($request);
    }
    
    public function dispatchToRoute(Request $request)
    {   
        // 先跳转到findRoute方法
        return $this->runRoute($request, $this->findRoute($request));
    }
    
    // 见名之意 通过给定的$request 找到匹配的路由
    protected function findRoute($request)
    {	
        // 跳转到IlluminateRoutingRouteCollection::match方法
        $this->current = $route = $this->routes->match($request);
        $this->container->instance(Route::class, $route);
        return $route;
    }
    

    查看IlluminateRoutingRouteCollection::match方法

    /**
     * Find the first route matching a given request.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateRoutingRoute
     *
     * @throws SymfonyComponentHttpKernelExceptionNotFoundHttpException
     */
    public function match(Request $request)
    {   
        // 根据请求动作找到全局匹配的路由
        // 可以自行打印下$routes
        $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;
    }
    

    下面说明框架如何加载的路由规则

    Application::boot方法

    // 主要逻辑是调用服务提供者的boot方法
    array_walk($this->serviceProviders, function ($p) {
        $this->bootProvider($p);
    });
    

    AppProvidersRouteServiceProvider::boot方法

    public function boot()
    {
        // 调用父类IlluminateFoundationSupportProvidersRouteServiceProvider的boot方法
        parent::boot();
    }
    

    IlluminateFoundationSupportProvidersRouteServiceProvider::boot方法

    public function boot()
    {   
        $this->setRootControllerNamespace();
    
        if ($this->routesAreCached()) {
            $this->loadCachedRoutes();
        } else {
            // 就看这个loadRoutes方法
            $this->loadRoutes();
    
            $this->app->booted(function () {
                // dd(get_class($this->app['router']));
                $this->app['router']->getRoutes()->refreshNameLookups();
                $this->app['router']->getRoutes()->refreshActionLookups();
            });
        }
    }
    
    /**
     * Load the application routes.
     * 看注释就知道我们来对了地方
     * @return void
     */
    protected function loadRoutes()
    {	
        // 调用AppProvidersRouteServiceProvider的map方法
        if (method_exists($this, 'map')) {
            $this->app->call([$this, 'map']);
        }
    }
    

    AppProvidersRouteServiceProvider::map方法

    public function map()
    {	
        // 为了调试方便我注释掉了api路由
        // $this->mapApiRoutes();
        
    	// 这两个都是加载路由文件 这里查看web.php
        $this->mapWebRoutes();
    }
    
    protected function mapWebRoutes()
    {	
        // 调用Router的__call方法 返回的是RouteRegistrar实例
        Route::middleware('web')
            ->namespace($this->namespace)
            // 调用RouteRegistrar的namespace方法 触发__call魔术方法
            
            // 依然是挂载属性 可自行打印
            // IlluminateRoutingRouteRegistrar {#239 ▼
            //   #router: IlluminateRoutingRouter {#34 ▶}
            //   #attributes: array:2 [▼
            //     "middleware" => array:1 [▼
            //       0 => "web"
            //     ]
            //     "namespace" => "AppHttpControllers"
            //   ]
            //   #passthru: array:7 [▶]
            //   #allowedAttributes: array:7 [▶]
            //   #aliases: array:1 [▶]
            // }
            
            // 调用RouteRegistrar的group方法
            ->group(base_path('routes/web.php'));
    }
    

    Router::__call方法

    public function __call($method, $parameters)
    {   
        if (static::hasMacro($method)) {
            return $this->macroCall($method, $parameters);
        }
    	
        if ($method === 'middleware') {
            // 调用了RouteRegistrar的attribute方法 只是挂载路由属性
            return (new RouteRegistrar($this))->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
        }
    
        return (new RouteRegistrar($this))->attribute($method, $parameters[0]);
    }
    

    IlluminateRoutingRouteRegistrar::__call方法

    public function __call($method, $parameters)
    {   
        if (in_array($method, $this->passthru)) {
            // 当使用get post等方法的时候
            return $this->registerRoute($method, ...$parameters);
        }
    
        if (in_array($method, $this->allowedAttributes)) {
            if ($method === 'middleware') {
                return $this->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
            }
            // dd($method); // namespace
            return $this->attribute($method, $parameters[0]);
        }
    
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }
    

    IlluminateRoutingRouteRegistrar::group方法

    public function group($callback)
    {   
        // dd($this->attributes, $callback);
        // array:2 [▼
        //     "middleware" => array:1 [▼
        //         0 => "web"
        //     ]
        //     "namespace" => "AppHttpControllers"
        // ]
        // "/home/vagrant/code/test1/routes/web.php"
        
        // 查看Router的group方法
        $this->router->group($this->attributes, $callback);
    }
    

    Router::group方法

    public function group(array $attributes, $routes)
    {
        $this->updateGroupStack($attributes);
    	
        // 查看loadRoutes方法 /home/vagrant/code/test1/routes/web.php
        $this->loadRoutes($routes);
    
        array_pop($this->groupStack);
    }
    
    protected function loadRoutes($routes)
    {	
        if ($routes instanceof Closure) {
            // 用于闭包嵌套 laravel的路由是可以随意潜逃组合的
            $routes($this);
        } else {
            // 加载路由文件 /home/vagrant/code/test1/routes/web.php
            (new RouteFileRegistrar($this))->register($routes);
        }
    }
    

    IlluminateRoutingRouteFileRegistrar 文件

    protected $router;
    
    public function __construct(Router $router)
    {   
        $this->router = $router;
    }
    
    public function register($routes)
    {
        $router = $this->router;
        // 终于加载到了路由文件
        // require("/home/vagrant/code/test1/routes/web.php");
        // 看到这里就到了大家熟悉的Route::get()等方法了
        // 道友们可能已经有了有趣的想法: 可以在web.php等路由文件中继续require其他文件
        // 便可实现不同功能模块的路由管理
        require $routes;
    }
    

    了解了理由加载流程,下面举个简单例子,laravel如何注册一个路由

    // web.php中
    Route::get('routecontroller', "AppHttpControllersDebugTestController@index");
    
    // 跳转到Router的get方法
    /**
     * Register a new GET route with the router.
     *
     * @param  string  $uri
     * @param  Closure|array|string|callable|null  $action
     * @return IlluminateRoutingRoute
     */
    public function get($uri, $action = null)
    {   
        // dump($uri, $action);
        // $uri = routecontroller
        // $action = AppHttpControllersDebugTestController@index
        // 跳转到addRoute方法
        return $this->addRoute(['GET', 'HEAD'], $uri, $action);
    }
    
    /**
     * Add a route to the underlying route collection.
     *
     * @param  array|string  $methods
     * @param  string  $uri
     * @param  Closure|array|string|callable|null  $action
     * @return IlluminateRoutingRoute
     */
    // 						['GET', 'HEAD'], $uri, $action
    public function addRoute($methods, $uri, $action)
    {   
        // routes是routecollection实例
        // 跳转到createRoute方法
        // 跳转到RouteCollection的add方法
        return $this->routes->add($this->createRoute($methods, $uri, $action));
    }
    
    /**
     * Create a new route instance.
     *
     * @param  array|string  $methods
     * @param  string  $uri
     * @param  mixed  $action
     * @return IlluminateRoutingRoute
     */
    //                             ['GET', 'HEAD'], $uri, $action
    protected function createRoute($methods, $uri, $action)
    {
        // 跳转到actionReferencesController方法
        if ($this->actionReferencesController($action)) {
            $action = $this->convertToControllerAction($action);
            // dump($action);
            // array:2 [▼
            //     "uses" => "AppHttpControllersDebugTestController@index"
            //     "controller" => "AppHttpControllersDebugTestController@index"
            // ]
        }
    	
        // 创建一个对应路由规则的Route实例 并且添加到routes(collection)中
        // 返回到上面的addRoute方法
        // 请自行查看Route的构造方法
        $route = $this->newRoute(
            // dump($this->prefix);
            // routecontroller
            $methods, $this->prefix($uri), $action
        );
    
        if ($this->hasGroupStack()) {
            $this->mergeGroupAttributesIntoRoute($route);
        }
    
        $this->addWhereClausesToRoute($route);
    
        return $route;
    }
    
    /**
     * Determine if the action is routing to a controller.
     *
     * @param  array  $action
     * @return bool
     */
    // 判断是否路由到一个控制器
    protected function actionReferencesController($action)
    {	
        // 在此例子中Route::get方法传递的是一个字符串
        if (! $action instanceof Closure) {
            // 返回true
            return is_string($action) || (isset($action['uses']) && is_string($action['uses']));
        }
    
        return false;
    }
    

    RouteCollection的add方法

    /**
         * Add a Route instance to the collection.
         *
         * @param  IlluminateRoutingRoute  $route
         * @return IlluminateRoutingRoute
         */
    public function add(Route $route)
    {	
        // 跳转吧
        $this->addToCollections($route);
    
        $this->addLookups($route);
    	
        // 最终一路返回到Router的get方法 所以我们可以直接打印web.php定义的路由规则
        return $route;
    }
    
    /**
     * Add the given route to the arrays of routes.
     *
     * @param  IlluminateRoutingRoute  $route
     * @return void
     */
    protected function addToCollections($route)
    {
        $domainAndUri = $route->getDomain().$route->uri();
        // dump($route->getDomain(), $route->uri()); null routecontroller
        foreach ($route->methods() as $method) {
            // 将路由规则挂载到数组 方便匹配
            $this->routes[$method][$domainAndUri] = $route;
        }
    		// 将路由规则挂载的数组 方便匹配
        $this->allRoutes[$method.$domainAndUri] = $route;
    }
    

    至此就生成了一条路由 注意我这里将注册api路由进行了注释,并且保证web.php中只有一条路由规则

    以上是路由的加载 这部分是在$this->bootstrap()方法中完成的,还远没有到达路由分发和匹配的阶段,希望大家能够理解,至此路由规则生成完毕 保存到了RouteCollection实例中,每个路由规则都是一个Route对象,供请求进行匹配

    下面根据此条路由进行匹配,并执行返回结果

    我们回到IlluminateRoutingRouteCollection::match方法

    public function match(Request $request)
    {   
    	// 获取符合当前请求动作的所有路由
        // 是一个Route对象数组 每一个对象对应一个route规则
        $routes = $this->get($request->getMethod());
    	
        // 匹配到当前请求路由
        $route = $this->matchAgainstRoutes($routes, $request);
    	
        if (! is_null($route)) {
            // 将绑定了请求的Route实例返回
            return $route->bind($request);
        }
    	
        $others = $this->checkForAlternateVerbs($request);
    
        if (count($others) > 0) {
            return $this->getRouteForMethods($request, $others);
        }
    
        throw new NotFoundHttpException;
    }
    
    // 该方法中大量使用了collect方法 请查看laravel手册
    protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
    {   
        // dump(get_class_methods(get_class(collect($routes))));
        // dump(collect($routes)->all()); // items数组 protected属性
        // dump(collect($routes)->items); // items属性是一个数组
        
        // 当注册一个兜底路由的时候 (通过Route::fallback方法)对应$route的isFallback会被设为true
        
        // partition方法根据传入的闭包将集合分成两部分
        // 具体实现可以查看手册 集合部分
        [$fallbacks, $routes] = collect($routes)->partition(function ($route) {
            return $route->isFallback;
        });
    	
        // 将兜底路由放到集合后面 并且通过first方法找到第一个匹配的路由
        return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) {
            return $value->matches($request, $includingMethod);
        });
    }
    

    Router文件

    protected function findRoute($request)
    {	
        // 可以打印$route 你会发现和你在web.php中打印的是同一个Route对象
        $this->current = $route = $this->routes->match($request);
    	// 将匹配到的路由实例挂载到容器
        $this->container->instance(Route::class, $route);
    
        return $route;
    }
    
    public function dispatchToRoute(Request $request)
    {   
    	// 跳转到runRoute方法
        return $this->runRoute($request, $this->findRoute($request));
    }
    
    protected function runRoute(Request $request, Route $route)
    {   
        // 给request帮顶当前的route 可以使用$request->route()方法 获取route实例
        // 你也可以随时在你的业务代码中通过容器获得当前Route实例 		
        // app(IlluminateRoutingRoute::class)
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });
    	
        $this->events->dispatch(new RouteMatched($route, $request));
    	
        // 开始准备响应了
        return $this->prepareResponse($request,
                                      // 跳转到runRouteWithinStack方法
                                      $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);
    	
        // 依旧是一个pipeline 我们跳转到$route->run方法
        return (new Pipeline($this->container))
            ->send($request)
            ->through($middleware)
            ->then(function ($request) use ($route) {
                return $this->prepareResponse(
                    
                    $request, $route->run()
                );
            });
    }
    

    Route::run方法 注意此方法的返回值是直接从匹配的控制器或者闭包中返回的

    public function run()
    {
        $this->container = $this->container ?: new Container;
    
        try {
            // 如果是一个控制器路由规则
            // 显然我们的此条路由是一个控制器路由
            if ($this->isControllerAction()) {
                // 将执行的结果返回给$route->run()
                // 跳回到上面的prepareResponse方法
                return $this->runController();
            }
    		
            // 如果是一个闭包路由规则ControllerDispatcher
            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }
    
    /**
     * Run the route action and return the response.
     *
     * @return mixed
     *
     * @throws SymfonyComponentHttpKernelExceptionNotFoundHttpException
     */
    protected function runController()
    {
        // 
        return $this->controllerDispatcher()->dispatch(
            $this,
            // 通过容器解析当前路由控制器实例
            $this->getController(),
            // 获取当前路由控制器方法
            $this->getControllerMethod()
        );
    }
    

    IlluminateRoutingControllerDispatcher::dispatch方法

    /**
     * Dispatch a request to a given controller and method.
     *
     * @param  IlluminateRoutingRoute  $route
     * @param  mixed  $controller
     * @param  string  $method
     * @return mixed
     */
    public function dispatch(Route $route, $controller, $method)
    {
        $parameters = $this->resolveClassMethodDependencies(
            $route->parametersWithoutNulls(), $controller, $method
        );
    
        if (method_exists($controller, 'callAction')) {
            // 执行基类控制器中的callAction方法并返回执行结果
            return $controller->callAction($method, $parameters);
        }
        
        return $controller->{$method}(...array_values($parameters));
    }
    

    控制器方法返回的结果到Router::runRouteWithinStack方法

    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(
                    // 返回到这里 然后执行prepareResponse方法
                    $request, $route->run()
                );
            });
    }
    
    // 实际调用的是toResponse方法
    // 注意这里的$response是直接从控制器中返回的任何东西
    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            // 我们当然可以直接从控制器中返回一个实现了Responsable接口的实例
            $response = $response->toResponse($request);
        }
    
        if ($response instanceof PsrResponseInterface) {
            // 什么??? laravel还支持psr7?? 当然了 后面会附上使用文档
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            // 知道为什么laravel允许直接返回一个模型了吗
            $response = new JsonResponse($response, 201);
        } elseif (! $response instanceof SymfonyResponse &&
                  // 知道laravel为什么允许你直接返回数组了吗
                  ($response instanceof Arrayable ||
                   $response instanceof Jsonable ||
                   $response instanceof ArrayObject ||
                   $response instanceof JsonSerializable ||
                   is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            // 如果没匹配到 比如response是一个字符串,null等 直接生成响应类
            // 我们从laravel的Response构造方法开始梳理
            $response = new Response($response);
        }
    
        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }
    	
        return $response->prepare($request);
    }
    

    首先我们来看直接生成laravel响应 IlluminateHttpResponse

    继承了SymfonyComponentHttpFoundationResponse

    // SymfonyComponentHttpFoundationResponse
    public function __construct($content = '', int $status = 200, array $headers = [])
    {	
        // 可以看到基本什么都没做
        $this->headers = new ResponseHeaderBag($headers);
        // 调用IlluminateHttpResponse的setContent方法 设置响应内容呗
        $this->setContent($content);
        $this->setStatusCode($status);
        $this->setProtocolVersion('1.0');
    }
    
    // IlluminateHttpResponse::setContent
    public function setContent($content)
    {
        $this->original = $content;
    	
        // shouldBeJson方法将实现了特定接口的response或者是一个array的response转换为
        // 并设置响应头
        if ($this->shouldBeJson($content)) {
            $this->header('Content-Type', 'application/json');
    		// morphToJson方法保证最终给此响应设置的响应内容为json串
            $content = $this->morphToJson($content);
        }
    	
        elseif ($content instanceof Renderable) {
            $content = $content->render();
        }
    	
        // SymfonyComponentHttpFoundationResponse 如果最终设置的响应内容不是null或者字符串或者实现了__toString方法的类 那么跑出异常, 否则设置响应内容
        parent::setContent($content);
    
        return $this;
    }
    
    // SymfonyComponentHttpFoundationResponse::setContent方法
    public function setContent($content)
    {
        if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([$content, '__toString'])) {
            // php官方建议不要使用gettype方法获取变量的类型
            throw new UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content)));
        }
    	// (string) 会触发__toString方法 如何对象允许的话
        $this->content = (string) $content;
        return $this;
    }
    

    拿到响应后执行return $response->prepare($request);

    /**
     * Prepares the Response before it is sent to the client.
     *
     * This method tweaks the Response to ensure that it is
     * compliant with RFC 2616. Most of the changes are based on
     * the Request that is "associated" with this Response.
     *
     * @return $this
     */
    // 总的来说就是设置各种响应头 注意此时并未发送响应
    public function prepare(Request $request)
    {	
        $headers = $this->headers;
    	// 如果是100 204 304系列的状态码 就删除响应数据 删除对应的数据头	
        if ($this->isInformational() || $this->isEmpty()) {
            $this->setContent(null);
            $headers->remove('Content-Type');
            $headers->remove('Content-Length');
        } else {
            // Content-type based on the Request
            if (!$headers->has('Content-Type')) {
                $format = $request->getPreferredFormat();
                if (null !== $format && $mimeType = $request->getMimeType($format)) {
                    $headers->set('Content-Type', $mimeType);
                }
            }
    
            // Fix Content-Type
            $charset = $this->charset ?: 'UTF-8';
            if (!$headers->has('Content-Type')) {
                $headers->set('Content-Type', 'text/html; charset='.$charset);
            } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
                // add the charset
                $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
            }
    
            // Fix Content-Length
            if ($headers->has('Transfer-Encoding')) {
                $headers->remove('Content-Length');
            }
    
            if ($request->isMethod('HEAD')) {
                // cf. RFC2616 14.13
                $length = $headers->get('Content-Length');
                $this->setContent(null);
                if ($length) {
                    $headers->set('Content-Length', $length);
                }
            }
        }
    
        // Fix protocol
        if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
            $this->setProtocolVersion('1.1');
        }
    
        // Check if we need to send extra expire info headers
        if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) {
            $headers->set('pragma', 'no-cache');
            $headers->set('expires', -1);
        }
    
        $this->ensureIEOverSSLCompatibility($request);
    
        if ($request->isSecure()) {
            foreach ($headers->getCookies() as $cookie) {
                $cookie->setSecureDefault(true);
            }
        }
    
        return $this;
    }
    
    // 至此我们的响应封装好了 等待发送给客户端
    // 在发送之前 还要将响应逐步返回
    // 值得注意的是 如果你给此路由设置了后置中间件 可能如下
    public function handle($request, Closure $next)
    {  	
        // 此时拿到的$response就是我们上面响应好了一切 准备发送的响应了 希望你能理解后置中间件的作用了
        $response = $next($request);
        // header方法位于ResponseTrait
        $response->header('Server', 'xy');
        return $response;
    }
    

    拿到准备好的响应了,逐级向调用栈行层返回,关系如下

    响应返回到Router::runRoute方法
    再返回到Router::dispatchToRoute方法
    再返回到Router::dispatch方法
    再返回到IlluminateFoundationHttp::sendRequestThroughRouter方法 (注意只要是通过了管道都要注意中间件的类型)
    最终返回到index.php中
        
    $response = $kernel->handle(
        $request = IlluminateHttpRequest::capture()
    );
    
    $response->send();
    
    $kernel->terminate($request, $response);
    

    我们来看send方法 SymfonyComponentHttpFoundationResponse::send

    public function send()
    {	
        // 先发送响应头
        $this->sendHeaders();
        // 再发送响应主体
        $this->sendContent();
    
        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } elseif (!in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
            static::closeOutputBuffers(0, true);
        }
    
        return $this;
    }
    
    public function sendHeaders()
    {
        // headers have already been sent by the developer
        if (headers_sent()) {
            return $this;
        }
    
        // headers
        foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
            $replace = 0 === strcasecmp($name, 'Content-Type');
            foreach ($values as $value) {
                // 将之前设置的各种头发送出去
                header($name.': '.$value, $replace, $this->statusCode);
            }
        }
    
        // cookies
        foreach ($this->headers->getCookies() as $cookie) {
            // 告诉客户端要设置的cookie
            header('Set-Cookie: '.$cookie, false, $this->statusCode);
        }
    
        // status
        // 最后发送个status
        header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
    
        return $this;
    }
    
    // 发送响应内容 
    public function sendContent()
    {	
        // 想笑吗 就是这么简单
        echo $this->content;
    
        return $this;
    }
    // 至此真的响应了客户端了
    

    $kernel->terminate($request, $response);

    IlluminateFoundationHttpKernel::terminate方法

    /**
     * Call the terminate method on any terminable middleware.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  IlluminateHttpResponse  $response
     * @return void
     */
    public function terminate($request, $response)
    {	
        // 调用实现了terminate方法的中间件
        $this->terminateMiddleware($request, $response);
    	// 执行注册的callback
        $this->app->terminate();
    }
    

    laravel将控制器(闭包)返回的数据封装成response对象

    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            $response = $response->toResponse($request);
        }
    
        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            $response = new JsonResponse($response, 201);
        } elseif (! $response instanceof SymfonyResponse &&
                  ($response instanceof Arrayable ||
                   $response instanceof Jsonable ||
                   $response instanceof ArrayObject ||
                   $response instanceof JsonSerializable ||
                   is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response);
        }
    
        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }
    
        return $response->prepare($request);
    }
    

    观察上面的代码发现:

    1 上面代码的作用是将路由节点返回的数据封装成Response对象等待发送

    2 并且上面的代码存在大量的instanceof判断 (为什么要这样呢 是因为一旦我们从控制器中返回一个实现了

    laravel指定接口的实例,laravel就知道该如何渲染这些响应给客户端 此时你可能还不清楚,请看下面的例子)

    3 而且没有else分支(这是因为laravel允许我们直接返回reponse对象,当我们直接返回Resposne实例的时候会直接走到方法的最后一句话)

    4 并且最终都调用的都是Symfony Response的prepare方法

    我们先来看Responsable接口 在laravel中任何一个实现了此接口的对象 都可以响应给客户端

    <?php
    
    namespace IlluminateContractsSupport;
    
    interface Responsable
    {
        /**
         * Create an HTTP response that represents the object.
         *
         * @param  IlluminateHttpRequest  $request
         * @return SymfonyComponentHttpFoundationResponse
         */
        // 接收$request参数
        // 返回Response对象
        public function toResponse($request);
    }
    
    
    // 下面我们在控制器中返回一个实现此接口的实例
    // 要实现的逻辑: 接收一个订单id 根据订单状态生成不同的响应,返回给客户端
    
    1 定义路由
    Route::get('yylh/{order}', "AppHttpControllersDebugTestController@checkStatus");
    
    2 创建响应
    namespace AppResponses;
    
    use AppModelsOrder;
    use IlluminateContractsSupportResponsable;
    use IlluminateHttpJsonResponse;
    
    class OrderStatusRes implements Responsable
    {
        protected $status;
    
        public function __construct(Order $order)
        {
            $this->status = $order->status;
        }
    
        public function toResponse($request)
        {
            if ($this->status) {
                // 订单以完成
                return new JsonResponse('order completed', 200);
            }
            // 订单未结算
            return view('needToCharge');
        }
    }
    
    3 创建控制器
    <?php
    
    namespace AppHttpControllersDebug;
    
    use AppHttpControllersController;
    use AppModelsOrder;
    use AppResponsesOrderStatusRes;
    
    class TestController extends Controller
    {
        public function checkStatus(Order $order)
        {
            return new OrderStatusRes($order);
        }
    }
    
    // 进行访问测试
    // http://homestead.test/yylh/1
    // http://homestead.test/yylh/2
    // 可以看到丧心病狂的我们 通过控制器中的一行代码 就实现了根据订单的不同状态回复了不同的响应
    // 我想说什么你们应该已经知道了
    

    看toResponse代码 我们发现 只要我们想办法返回符合laravel规定的数据,最终都会被转换成laravel response实例 比如我们可以返回Responsable实例,Arrayable实例,Jsonable实例等等,大家可以尝试直接返回return new Response(),Response::create等等

    Route::get('rawReponse', function () {

    ​ return new Response(range(1,10));

    });

    更多请查看这位老哥的博客

    通过十篇水文,分享了从类的自动加载,到走完laravel的生命周期。

    第一次写博客不足太多,不爱看大量代码的道友,可以查看这位外国老哥的博客,其代码较少,但逻辑清晰明了。发现错误,欢迎指导,感谢!!!

    collection文档

    laravel中使用psr7

  • 相关阅读:
    [leetcode] Combination Sum and Combination SumII
    nginx随着passenger构造ruby on rails页
    form 为什么上传文件enctype现场
    ftk学习记录(多形式的文章)
    Android setDisplayOptions 具体的使用说明
    存储结构二叉树
    SQLSERVER存储过程语法的具体解释
    iOS多用连接、反向协议、安全
    struts2于validate要使用
    Oracle存储过程实现返回多个结果集 在构造函数方法中使用 dataset
  • 原文地址:https://www.cnblogs.com/alwayslinger/p/13601782.html
Copyright © 2020-2023  润新知