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

查看Illuminate\Routing\Router::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)
{	
    // 跳轉到Illuminate\Routing\RouteCollection::match方法
    $this->current = $route = $this->routes->match($request);
    $this->container->instance(Route::class, $route);
    return $route;
}

查看Illuminate\Routing\RouteCollection::match方法

/**
 * Find the first route matching a given request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Routing\Route
 *
 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
 */
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);
});

App\Providers\RouteServiceProvider::boot方法

public function boot()
{
    // 調用父類Illuminate\Foundation\Support\Providers\RouteServiceProvider的boot方法
    parent::boot();
}

Illuminate\Foundation\Support\Providers\RouteServiceProvider::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()
{	
    // 調用App\Providers\RouteServiceProvider的map方法
    if (method_exists($this, 'map')) {
        $this->app->call([$this, 'map']);
    }
}

App\Providers\RouteServiceProvider::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魔術方法
        
        // 依然是掛載屬性 可自行列印
        // Illuminate\Routing\RouteRegistrar {#239 ▼
        //   #router: Illuminate\Routing\Router {#34 ▶}
        //   #attributes: array:2 [▼
        //     "middleware" => array:1 [▼
        //       0 => "web"
        //     ]
        //     "namespace" => "App\Http\Controllers"
        //   ]
        //   #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]);
}

Illuminate\Routing\RouteRegistrar::__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
    ));
}

Illuminate\Routing\RouteRegistrar::group方法

public function group($callback)
{   
    // dd($this->attributes, $callback);
    // array:2 [▼
    //     "middleware" => array:1 [▼
    //         0 => "web"
    //     ]
    //     "namespace" => "App\Http\Controllers"
    // ]
    // "/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);
    }
}

Illuminate\Routing\RouteFileRegistrar 文件

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', "\App\Http\Controllers\Debug\TestController@index");

// 跳轉到Router的get方法
/**
 * Register a new GET route with the router.
 *
 * @param  string  $uri
 * @param  \Closure|array|string|callable|null  $action
 * @return \Illuminate\Routing\Route
 */
public function get($uri, $action = null)
{   
    // dump($uri, $action);
    // $uri = routecontroller
    // $action = \App\Http\Controllers\Debug\TestController@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 \Illuminate\Routing\Route
 */
// 						['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 \Illuminate\Routing\Route
 */
//                             ['GET', 'HEAD'], $uri, $action
protected function createRoute($methods, $uri, $action)
{
    // 跳轉到actionReferencesController方法
    if ($this->actionReferencesController($action)) {
        $action = $this->convertToControllerAction($action);
        // dump($action);
        // array:2 [▼
        //     "uses" => "\App\Http\Controllers\Debug\TestController@index"
        //     "controller" => "\App\Http\Controllers\Debug\TestController@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  \Illuminate\Routing\Route  $route
     * @return \Illuminate\Routing\Route
     */
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  \Illuminate\Routing\Route  $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對象,供請求進行匹配

下面根據此條路由進行匹配,並執行返回結果

我們回到Illuminate\Routing\RouteCollection::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(Illuminate\Routing\Route::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 \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
 */
protected function runController()
{
    // 
    return $this->controllerDispatcher()->dispatch(
        $this,
        // 通過容器解析當前路由控制器實例
        $this->getController(),
        // 獲取當前路由控制器方法
        $this->getControllerMethod()
    );
}

Illuminate\Routing\ControllerDispatcher::dispatch方法

/**
 * Dispatch a request to a given controller and method.
 *
 * @param  \Illuminate\Routing\Route  $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響應 Illuminate\Http\Response

繼承了Symfony\Component\HttpFoundation\Response

// Symfony\Component\HttpFoundation\Response
public function __construct($content = '', int $status = 200, array $headers = [])
{	
    // 可以看到基本什麼都沒做
    $this->headers = new ResponseHeaderBag($headers);
    // 調用Illuminate\Http\Response的setContent方法 設置響應內容唄
    $this->setContent($content);
    $this->setStatusCode($status);
    $this->setProtocolVersion('1.0');
}

// Illuminate\Http\Response::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();
    }
	
    // Symfony\Component\HttpFoundation\Response 如果最終設置的響應內容不是null或者字元串或者實現了__toString方法的類 那麼跑出異常, 否則設置響應內容
    parent::setContent($content);

    return $this;
}

// Symfony\Component\HttpFoundation\Response::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方法
再返回到Illuminate\Foundation\Http::sendRequestThroughRouter方法 (注意只要是通過了管道都要注意中間件的類型)
最終返回到index.php中
    
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

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

我們來看send方法 Symfony\Component\HttpFoundation\Response::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);

Illuminate\Foundation\Http\Kernel::terminate方法

/**
 * Call the terminate method on any terminable middleware.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Http\Response  $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);
}

觀察上面的程式碼發現:

上面程式碼的作用是將路由節點返回的數據封裝成Response對象等待發送

並且上面的程式碼存在大量的instanceof判斷 (為什麼要這樣呢 是因為一旦我們從控制器中返回一個實現了

laravel指定介面的實例,laravel就知道該如何渲染這些響應給客戶端 此時你可能還不清楚,請看下面的例子)

而且沒有else分支(這是因為laravel允許我們直接返回reponse對象,當我們直接返回Resposne實例的時候會直接走到方法的最後一句話)

並且最終都調用的都是Symfony Response的prepare方法

我們先來看Responsable介面 在laravel中任何一個實現了此介面的對象 都可以響應給客戶端

<?php

namespace Illuminate\Contracts\Support;

interface Responsable
{
    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    // 接收$request參數
    // 返回Response對象
    public function toResponse($request);
}


// 下面我們在控制器中返回一個實現此介面的實例
// 要實現的邏輯: 接收一個訂單id 根據訂單狀態生成不同的響應,返回給客戶端

1 定義路由
Route::get('yylh/{order}', "\App\Http\Controllers\Debug\TestController@checkStatus");

2 創建響應
namespace App\Responses;

use App\Models\Order;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\JsonResponse;

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 App\Http\Controllers\Debug;

use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Responses\OrderStatusRes;

class TestController extends Controller
{
    public function checkStatus(Order $order)
    {
        return new OrderStatusRes($order);
    }
}

// 進行訪問測試
// //homestead.test/yylh/1
// //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

Tags: