溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

在Laravel使用Reponse實現(xiàn)一個響應(yīng)客戶端功能

發(fā)布時間:2020-11-05 15:52:38 來源:億速云 閱讀:198 作者:Leah 欄目:開發(fā)技術(shù)

在Laravel使用Reponse實現(xiàn)一個響應(yīng)客戶端功能?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

1、執(zhí)行上文管道中的then方法指定的閉包,路由的分發(fā)

2、在路由器中(Router類)找到請求($request 也就是經(jīng)過全局中間件處理的請求)匹配的路由規(guī)則

3、說明路由規(guī)則的加載(會跳轉(zhuǎn)到框架的boot過程),注意這部分是在處理請求之前完成的,因為一旦當(dāng)我們開始處理請求,就意味著所有的路由都應(yīng)該已經(jīng)加載好了,供我們的請求進(jìn)行匹配

4、執(zhí)行請求匹配到的路由邏輯

5、生成響應(yīng),并發(fā)送給客戶端

6、最后生命周期的結(jié)束

7、基本響應(yīng)類的使用

前文說道,如果一個請求順利通過了全局中間件那么就會調(diào)用管道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)
 // 此方法將當(dāng)前請求掛載到容器,然后執(zhí)行路由器的分發(fā)
 ->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;
 // 將請求分發(fā)到路由
 // 跳轉(zhuǎn)到dispatchToRoute方法
 return $this->dispatchToRoute($request);
}

public function dispatchToRoute(Request $request)
{ 
 // 先跳轉(zhuǎn)到findRoute方法
 return $this->runRoute($request, $this->findRoute($request));
}

// 見名之意 通過給定的$request 找到匹配的路由
protected function findRoute($request)
{ 
 // 跳轉(zhuǎn)到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)
{ 
 // 根據(jù)請求動作找到全局匹配的路由
 // 可以自行打印下$routes
 $routes = $this->get($request->getMethod());
 // 匹配路由 下面查看框架如何生成的路由規(guī)則!!!
 $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;
}

下面說明框架如何加載的路由規(guī)則

Application::boot方法

// 主要邏輯是調(diào)用服務(wù)提供者的boot方法
array_walk($this->serviceProviders, function ($p) {
 $this->bootProvider($p);
});

App\Providers\RouteServiceProvider::boot方法

public function boot()
{
 // 調(diào)用父類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()
{ 
 // 調(diào)用App\Providers\RouteServiceProvider的map方法
 if (method_exists($this, 'map')) {
 $this->app->call([$this, 'map']);
 }
}

App\Providers\RouteServiceProvider::map方法

public function map()
{ 
 // 為了調(diào)試方便我注釋掉了api路由
 // $this->mapApiRoutes();
 
 // 這兩個都是加載路由文件 這里查看web.php
 $this->mapWebRoutes();
}

protected function mapWebRoutes()
{ 
 // 調(diào)用Router的__call方法 返回的是RouteRegistrar實例
 Route::middleware('web')
 ->namespace($this->namespace)
 // 調(diào)用RouteRegistrar的namespace方法 觸發(fā)__call魔術(shù)方法
 
 // 依然是掛載屬性 可自行打印
 // 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 [▶]
 // }
 
 // 調(diào)用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') {
 // 調(diào)用了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)) {
  // 當(dāng)使用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()等方法了
 // 道友們可能已經(jīng)有了有趣的想法: 可以在web.php等路由文件中繼續(xù)require其他文件
 // 便可實現(xiàn)不同功能模塊的路由管理
 require $routes;
}

了解了理由加載流程,下面舉個簡單例子,laravel如何注冊一個路由

// web.php中
Route::get('routecontroller', "\App\Http\Controllers\Debug\TestController@index");

// 跳轉(zhuǎn)到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
 // 跳轉(zhuǎn)到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實例
 // 跳轉(zhuǎn)到createRoute方法
 // 跳轉(zhuǎn)到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)
{
 // 跳轉(zhuǎn)到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"
  // ]
 }
 
 // 創(chuàng)建一個對應(yīng)路由規(guī)則的Route實例 并且添加到routes(collection)中
 // 返回到上面的addRoute方法
 // 請自行查看Route的構(gòu)造方法
 $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)
{ 
 // 跳轉(zhuǎn)吧
 $this->addToCollections($route);

 $this->addLookups($route);
 
 // 最終一路返回到Router的get方法 所以我們可以直接打印web.php定義的路由規(guī)則
 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) {
  // 將路由規(guī)則掛載到數(shù)組 方便匹配
  $this->routes[$method][$domainAndUri] = $route;
 }
 // 將路由規(guī)則掛載的數(shù)組 方便匹配
 $this->allRoutes[$method.$domainAndUri] = $route;
}

至此就生成了一條路由 注意我這里將注冊api路由進(jìn)行了注釋,并且保證web.php中只有一條路由規(guī)則

以上是路由的加載 這部分是在$this->bootstrap()方法中完成的,還遠(yuǎn)沒有到達(dá)路由分發(fā)和匹配的階段,希望大家能夠理解,至此路由規(guī)則生成完畢 保存到了RouteCollection實例中,每個路由規(guī)則都是一個Route對象,供請求進(jìn)行匹配

下面根據(jù)此條路由進(jìn)行匹配,并執(zhí)行返回結(jié)果

我們回到Illuminate\Routing\RouteCollection::match方法

public function match(Request $request)
{ 
 // 獲取符合當(dāng)前請求動作的所有路由
 // 是一個Route對象數(shù)組 每一個對象對應(yīng)一個route規(guī)則
 $routes = $this->get($request->getMethod());
 
 // 匹配到當(dāng)前請求路由
 $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數(shù)組 protected屬性
 // dump(collect($routes)->items); // items屬性是一個數(shù)組
 
 // 當(dāng)注冊一個兜底路由的時候 (通過Route::fallback方法)對應(yīng)$route的isFallback會被設(shè)為true
 
 // partition方法根據(jù)傳入的閉包將集合分成兩部分
 // 具體實現(xiàn)可以查看手冊 集合部分
 [$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 你會發(fā)現(xiàn)和你在web.php中打印的是同一個Route對象
 $this->current = $route = $this->routes->match($request);
 // 將匹配到的路由實例掛載到容器
 $this->container->instance(Route::class, $route);

 return $route;
}

public function dispatchToRoute(Request $request)
{ 
 // 跳轉(zhuǎn)到runRoute方法
 return $this->runRoute($request, $this->findRoute($request));
}

protected function runRoute(Request $request, Route $route)
{ 
 // 給request幫頂當(dāng)前的route 可以使用$request->route()方法 獲取route實例
 // 你也可以隨時在你的業(yè)務(wù)代碼中通過容器獲得當(dāng)前Route實例 
 // app(Illuminate\Routing\Route::class)
 $request->setRouteResolver(function () use ($route) {
  return $route;
 });
 
 $this->events->dispatch(new RouteMatched($route, $request));
 
 // 開始準(zhǔn)備響應(yīng)了
 return $this->prepareResponse($request,
         // 跳轉(zhuǎn)到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 我們跳轉(zhuǎn)到$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 {
  // 如果是一個控制器路由規(guī)則
  // 顯然我們的此條路由是一個控制器路由
  if ($this->isControllerAction()) {
   // 將執(zhí)行的結(jié)果返回給$route->run()
   // 跳回到上面的prepareResponse方法
   return $this->runController();
  }
 
  // 如果是一個閉包路由規(guī)則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,
  // 通過容器解析當(dāng)前路由控制器實例
  $this->getController(),
  // 獲取當(dāng)前路由控制器方法
  $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')) {
  // 執(zhí)行基類控制器中的callAction方法并返回執(zhí)行結(jié)果
  return $controller->callAction($method, $parameters);
 }
 
 return $controller->{$method}(...array_values($parameters));
}

控制器方法返回的結(jié)果到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(
    // 返回到這里 然后執(zhí)行prepareResponse方法
    $request, $route->run()
   );
  });
}

// 實際調(diào)用的是toResponse方法
// 注意這里的$response是直接從控制器中返回的任何東西
public static function toResponse($request, $response)
{
 if ($response instanceof Responsable) {
  // 我們當(dāng)然可以直接從控制器中返回一個實現(xiàn)了Responsable接口的實例
  $response = $response->toResponse($request);
 }

 if ($response instanceof PsrResponseInterface) {
  // 什么??? laravel還支持psr7?? 當(dāng)然了 后面會附上使用文檔
  $response = (new HttpFoundationFactory)->createResponse($response);
 } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
  // 知道為什么laravel允許直接返回一個模型了嗎
  $response = new JsonResponse($response, 201);
 } elseif (! $response instanceof SymfonyResponse &&
    // 知道laravel為什么允許你直接返回數(shù)組了嗎
    ($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等 直接生成響應(yīng)類
  // 我們從laravel的Response構(gòu)造方法開始梳理
  $response = new Response($response);
 }

 if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
  $response->setNotModified();
 }
 
 return $response->prepare($request);
}

首先我們來看直接生成laravel響應(yīng) 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);
 // 調(diào)用Illuminate\Http\Response的setContent方法 設(shè)置響應(yīng)內(nèi)容唄
 $this->setContent($content);
 $this->setStatusCode($status);
 $this->setProtocolVersion('1.0');
}

// Illuminate\Http\Response::setContent
public function setContent($content)
{
 $this->original = $content;
 
 // shouldBeJson方法將實現(xiàn)了特定接口的response或者是一個array的response轉(zhuǎn)換為
 // 并設(shè)置響應(yīng)頭
 if ($this->shouldBeJson($content)) {
  $this->header('Content-Type', 'application/json');
 // morphToJson方法保證最終給此響應(yīng)設(shè)置的響應(yīng)內(nèi)容為json串
  $content = $this->morphToJson($content);
 }
 
 elseif ($content instanceof Renderable) {
  $content = $content->render();
 }
 
 // Symfony\Component\HttpFoundation\Response 如果最終設(shè)置的響應(yīng)內(nèi)容不是null或者字符串或者實現(xiàn)了__toString方法的類 那么跑出異常, 否則設(shè)置響應(yīng)內(nèi)容
 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) 會觸發(fā)__toString方法 如何對象允許的話
 $this->content = (string) $content;
 return $this;
}

拿到響應(yīng)后執(zhí)行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
 */
// 總的來說就是設(shè)置各種響應(yīng)頭 注意此時并未發(fā)送響應(yīng)
public function prepare(Request $request)
{ 
 $headers = $this->headers;
 // 如果是100 204 304系列的狀態(tài)碼 就刪除響應(yīng)數(shù)據(jù) 刪除對應(yīng)的數(shù)據(jù)頭 
 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;
}

// 至此我們的響應(yīng)封裝好了 等待發(fā)送給客戶端
// 在發(fā)送之前 還要將響應(yīng)逐步返回
// 值得注意的是 如果你給此路由設(shè)置了后置中間件 可能如下
public function handle($request, Closure $next)
{ 
 // 此時拿到的$response就是我們上面響應(yīng)好了一切 準(zhǔn)備發(fā)送的響應(yīng)了 希望你能理解后置中間件的作用了
 $response = $next($request);
 // header方法位于ResponseTrait
 $response->header('Server', 'xy');
 return $response;
}

拿到準(zhǔn)備好的響應(yīng)了,逐級向調(diào)用棧行層返回,關(guān)系如下

響應(yīng)返回到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()
{ 
 // 先發(fā)送響應(yīng)頭
 $this->sendHeaders();
 // 再發(fā)送響應(yīng)主體
 $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) {
   // 將之前設(shè)置的各種頭發(fā)送出去
   header($name.': '.$value, $replace, $this->statusCode);
  }
 }

 // cookies
 foreach ($this->headers->getCookies() as $cookie) {
  // 告訴客戶端要設(shè)置的cookie
  header('Set-Cookie: '.$cookie, false, $this->statusCode);
 }

 // status
 // 最后發(fā)送個status
 header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

 return $this;
}

// 發(fā)送響應(yīng)內(nèi)容 
public function sendContent()
{ 
 // 想笑嗎 就是這么簡單
 echo $this->content;

 return $this;
}
// 至此真的響應(yīng)了客戶端了

$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)
{ 
 // 調(diào)用實現(xiàn)了terminate方法的中間件
 $this->terminateMiddleware($request, $response);
 // 執(zhí)行注冊的callback
 $this->app->terminate();
}

laravel將控制器(閉包)返回的數(shù)據(jù)封裝成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);
}

觀察上面的代碼發(fā)現(xiàn):

1 上面代碼的作用是將路由節(jié)點返回的數(shù)據(jù)封裝成Response對象等待發(fā)送

2 并且上面的代碼存在大量的instanceof判斷 (為什么要這樣呢 是因為一旦我們從控制器中返回一個實現(xiàn)了

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

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

4 并且最終都調(diào)用的都是Symfony Response的prepare方法

我們先來看Responsable接口 在laravel中任何一個實現(xiàn)了此接口的對象 都可以響應(yīng)給客戶端

<&#63;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參數(shù)
 // 返回Response對象
 public function toResponse($request);
}


// 下面我們在控制器中返回一個實現(xiàn)此接口的實例
// 要實現(xiàn)的邏輯: 接收一個訂單id 根據(jù)訂單狀態(tài)生成不同的響應(yīng),返回給客戶端

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

2 創(chuàng)建響應(yīng)
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);
  }
  // 訂單未結(jié)算
  return view('needToCharge');
 }
}

3 創(chuàng)建控制器
<&#63;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);
 }
}

// 進(jìn)行訪問測試
// http://homestead.test/yylh/1
// http://homestead.test/yylh/2
// 可以看到喪心病狂的我們 通過控制器中的一行代碼 就實現(xiàn)了根據(jù)訂單的不同狀態(tài)回復(fù)了不同的響應(yīng)
// 我想說什么你們應(yīng)該已經(jīng)知道了

看toResponse代碼 我們發(fā)現(xiàn) 只要我們想辦法返回符合laravel規(guī)定的數(shù)據(jù),最終都會被轉(zhuǎn)換成laravel response實例 比如我們可以返回Responsable實例,Arrayable實例,Jsonable實例等等,大家可以嘗試直接返回return new Response(),Response::create等等

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

&#8203; return new Response(range(1,10));

});


看完上述內(nèi)容,你們掌握在Laravel使用Reponse實現(xiàn)一個響應(yīng)客戶端功能的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI