当前位置:首页 > CMS教程 > 其它CMS > 列表

Laravel Reponse响应客户端示例详解

发布:smiling 来源: PHP粉丝网  添加日期:2022-03-25 11:17:21 浏览: 评论:0 

这篇文章主要给大家介绍了关于Laravel Reponse响应客户端的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

前言

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

本文主要内容顺序为:
1、执行上文管道中的then方法指定的闭包,路由的分发

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

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

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

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

6、最后生命周期的结束

7、基本响应类的使用

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

  1. protected function sendRequestThroughRouter($request
  2.  $this->app->instance('request'$request); 
  3.  Facade::clearResolvedInstance('request'); 
  4.  
  5.  $this->bootstrap(); 
  6.    
  7.  // 代码如下 
  8.  return (new Pipeline($this->app)) 
  9.  ->send($request
  10.  ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 
  11.  // 此方法将当前请求挂载到容器,然后执行路由器的分发 
  12.  ->then($this->dispatchToRouter()); 
  13.  
  14. protected function dispatchToRouter() 
  15.  return function ($request) { 
  16.  $this->app->instance('request'$request); 
  17.  return $this->router->dispatch($request); 
  18.  }; 

查看Illuminate\Routing\Router::dispatch方法

  1. public function dispatch(Request $request
  2. {  
  3.  $this->currentRequest = $request
  4.  // 将请求分发到路由 
  5.  // 跳转到dispatchToRoute方法 
  6.  return $this->dispatchToRoute($request); 
  7.  
  8. public function dispatchToRoute(Request $request
  9. {  
  10.  // 先跳转到findRoute方法 
  11.  return $this->runRoute($request$this->findRoute($request)); 
  12.  
  13. // 见名之意 通过给定的$request 找到匹配的路由 
  14. protected function findRoute($request
  15. {  
  16.  // 跳转到Illuminate\Routing\RouteCollection::match方法 
  17.  $this->current = $route = $this->routes->match($request); 
  18.  $this->container->instance(Route::class$route); 
  19.  return $route

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

  1. /** 
  2.  * Find the first route matching a given request. 
  3.  * 
  4.  * @param \Illuminate\Http\Request $request 
  5.  * @return \Illuminate\Routing\Route 
  6.  * 
  7.  * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException 
  8.  */ 
  9. public function match(Request $request
  10. {  
  11.  // 根据请求动作找到全局匹配的路由 
  12.  // 可以自行打印下$routes 
  13.  $routes = $this->get($request->getMethod()); 
  14.  // 匹配路由 下面查看框架如何生成的路由规则!!! 
  15.  $route = $this->matchAgainstRoutes($routes$request); 
  16.    
  17.  if (! is_null($route)) { 
  18.  return $route->bind($request); 
  19.  } 
  20.  
  21.  $others = $this->checkForAlternateVerbs($request); 
  22.  
  23.  if (count($others) > 0) { 
  24.  return $this->getRouteForMethods($request$others); 
  25.  } 
  26.  
  27.  throw new NotFoundHttpException; 

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

Application::boot方法

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

App\Providers\RouteServiceProvider::boot方法

  1. public function boot() 
  2.  // 调用父类Illuminate\Foundation\Support\Providers\RouteServiceProvider的boot方法 
  3.  parent::boot(); 

Illuminate\Foundation\Support\Providers\RouteServiceProvider::boot方法

  1. public function boot() 
  2. {  
  3.  $this->setRootControllerNamespace(); 
  4.  
  5.  if ($this->routesAreCached()) { 
  6.  $this->loadCachedRoutes(); 
  7.  } else { 
  8.  // 就看这个loadRoutes方法 
  9.  $this->loadRoutes(); 
  10.  
  11.  $this->app->booted(function () { 
  12.   // dd(get_class($this->app['router'])); 
  13.   $this->app['router']->getRoutes()->refreshNameLookups(); 
  14.   $this->app['router']->getRoutes()->refreshActionLookups(); 
  15.  }); 
  16.  } 
  17.  
  18. /** 
  19.  * Load the application routes. 
  20.  * 看注释就知道我们来对了地方 
  21.  * @return void 
  22.  */ 
  23. protected function loadRoutes() 
  24. {  
  25.  // 调用App\Providers\RouteServiceProvider的map方法 
  26.  if (method_exists($this'map')) { 
  27.  $this->app->call([$this'map']); 
  28.  } 

App\Providers\RouteServiceProvider::map方法

  1. public function map() 
  2. {  
  3.  // 为了调试方便我注释掉了api路由 
  4.  // $this->mapApiRoutes(); 
  5.    
  6.  // 这两个都是加载路由文件 这里查看web.php 
  7.  $this->mapWebRoutes(); 
  8.  
  9. protected function mapWebRoutes() 
  10. {  
  11.  // 调用Router的__call方法 返回的是RouteRegistrar实例 
  12.  Route::middleware('web'
  13.  ->namespace($this->namespace) 
  14.  // 调用RouteRegistrar的namespace方法 触发__call魔术方法 
  15.    
  16.  // 依然是挂载属性 可自行打印 
  17.  // Illuminate\Routing\RouteRegistrar {#239 ▼ 
  18.  // #router: Illuminate\Routing\Router {#34 ▶} 
  19.  // #attributes: array:2 [▼ 
  20.  // "middleware" => array:1 [▼ 
  21.  // 0 => "web" 
  22.  // ] 
  23.  // "namespace" => "App\Http\Controllers" 
  24.  // ] 
  25.  // #passthru: array:7 [▶] 
  26.  // #allowedAttributes: array:7 [▶] 
  27.  // #aliases: array:1 [▶] 
  28.  // } 
  29.    
  30.  // 调用RouteRegistrar的group方法 
  31.  ->group(base_path('routes/web.php')); 

Router::__call方法

  1. public function __call($method$parameters
  2. {  
  3.  if (static::hasMacro($method)) { 
  4.  return $this->macroCall($method$parameters); 
  5.  } 
  6.    
  7.  if ($method === 'middleware') { 
  8.  // 调用了RouteRegistrar的attribute方法 只是挂载路由属性 
  9.  return (new RouteRegistrar($this))->attribute($methodis_array($parameters[0]) ? $parameters[0] : $parameters); 
  10.  } 
  11.  
  12.  return (new RouteRegistrar($this))->attribute($method$parameters[0]); 

Illuminate\Routing\RouteRegistrar::__call方法

  1. public function __call($method$parameters
  2. {  
  3.  if (in_array($method$this->passthru)) { 
  4.   // 当使用get post等方法的时候 
  5.   return $this->registerRoute($method, ...$parameters); 
  6.  } 
  7.  
  8.  if (in_array($method$this->allowedAttributes)) { 
  9.   if ($method === 'middleware') { 
  10.    return $this->attribute($methodis_array($parameters[0]) ? $parameters[0] : $parameters); 
  11.   } 
  12.   // dd($method); // namespace 
  13.   return $this->attribute($method$parameters[0]); 
  14.  } 
  15.  
  16.  throw new BadMethodCallException(sprintf( 
  17.   'Method %s::%s does not exist.'static::class$method 
  18.  )); 

Illuminate\Routing\RouteRegistrar::group方法

  1. public function group($callback
  2. {  
  3.  // dd($this->attributes, $callback); 
  4.  // array:2 [▼ 
  5.  //  "middleware" => array:1 [▼ 
  6.  //   0 => "web" 
  7.  //  ] 
  8.  //  "namespace" => "App\Http\Controllers" 
  9.  // ] 
  10.  // "/home/vagrant/code/test1/routes/web.php" 
  11.    
  12.  // 查看Router的group方法 
  13.  $this->router->group($this->attributes, $callback); 

Router::group方法

  1. public function group(array $attributes$routes
  2.  $this->updateGroupStack($attributes); 
  3.    
  4.  // 查看loadRoutes方法 /home/vagrant/code/test1/routes/web.php 
  5.  $this->loadRoutes($routes); 
  6.  
  7.  array_pop($this->groupStack); 
  8.  
  9. protected function loadRoutes($routes
  10. {  
  11.  if ($routes instanceof Closure) { 
  12.   // 用于闭包嵌套 laravel的路由是可以随意潜逃组合的 
  13.   $routes($this); 
  14.  } else { 
  15.   // 加载路由文件 /home/vagrant/code/test1/routes/web.php 
  16.   (new RouteFileRegistrar($this))->register($routes); 
  17.  } 

Illuminate\Routing\RouteFileRegistrar 文件

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

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

  1. // web.php中 
  2. Route::get('routecontroller'"\App\Http\Controllers\Debug\TestController@index"); 
  3.  
  4. // 跳转到Router的get方法 
  5. /** 
  6.  * Register a new GET route with the router. 
  7.  * 
  8.  * @param string $uri 
  9.  * @param \Closure|array|string|callable|null $action 
  10.  * @return \Illuminate\Routing\Route 
  11.  */ 
  12. public function get($uri$action = null) 
  13. {  
  14.  // dump($uri, $action); 
  15.  // $uri = routecontroller 
  16.  // $action = \App\Http\Controllers\Debug\TestController@index 
  17.  // 跳转到addRoute方法 
  18.  return $this->addRoute(['GET''HEAD'], $uri$action); 
  19.  
  20. /** 
  21.  * Add a route to the underlying route collection. 
  22.  * 
  23.  * @param array|string $methods 
  24.  * @param string $uri 
  25.  * @param \Closure|array|string|callable|null $action 
  26.  * @return \Illuminate\Routing\Route 
  27.  */ 
  28. //  ['GET', 'HEAD'], $uri, $action 
  29. public function addRoute($methods$uri$action
  30. {  
  31.  // routes是routecollection实例 
  32.  // 跳转到createRoute方法 
  33.  // 跳转到RouteCollection的add方法 
  34.  return $this->routes->add($this->createRoute($methods$uri$action)); 
  35.  
  36. /** 
  37.  * Create a new route instance. 
  38.  * 
  39.  * @param array|string $methods 
  40.  * @param string $uri 
  41.  * @param mixed $action 
  42.  * @return \Illuminate\Routing\Route 
  43.  */ 
  44. //        ['GET', 'HEAD'], $uri, $action 
  45. protected function createRoute($methods$uri$action
  46.  // 跳转到actionReferencesController方法 
  47.  if ($this->actionReferencesController($action)) { 
  48.   $action = $this->convertToControllerAction($action); 
  49.   // dump($action); 
  50.   // array:2 [▼ 
  51.   //  "uses" => "\App\Http\Controllers\Debug\TestController@index" 
  52.   //  "controller" => "\App\Http\Controllers\Debug\TestController@index" 
  53.   // ] 
  54.  } 
  55.    
  56.  // 创建一个对应路由规则的Route实例 并且添加到routes(collection)中 
  57.  // 返回到上面的addRoute方法 
  58.  // 请自行查看Route的构造方法 
  59.  $route = $this->newRoute( 
  60.   // dump($this->prefix); 
  61.   // routecontroller 
  62.   $methods$this->prefix($uri), $action 
  63.  ); 
  64.  
  65.  if ($this->hasGroupStack()) { 
  66.   $this->mergeGroupAttributesIntoRoute($route); 
  67.  } 
  68.  
  69.  $this->addWhereClausesToRoute($route); 
  70.  
  71.  return $route
  72.  
  73. /** 
  74.  * Determine if the action is routing to a controller. 
  75.  * 
  76.  * @param array $action 
  77.  * @return bool 
  78.  */ 
  79. // 判断是否路由到一个控制器 
  80. protected function actionReferencesController($action
  81. {  
  82.  // 在此例子中Route::get方法传递的是一个字符串 
  83.  if (! $action instanceof Closure) { 
  84.   // 返回true 
  85.   return is_string($action) || (isset($action['uses']) && is_string($action['uses'])); 
  86.  } 
  87.  
  88.  return false; 

RouteCollection的add方法

  1. /** 
  2.   * Add a Route instance to the collection. 
  3.   * 
  4.   * @param \Illuminate\Routing\Route $route 
  5.   * @return \Illuminate\Routing\Route 
  6.   */ 
  7. public function add(Route $route
  8. {  
  9.  // 跳转吧 
  10.  $this->addToCollections($route); 
  11.  
  12.  $this->addLookups($route); 
  13.    
  14.  // 最终一路返回到Router的get方法 所以我们可以直接打印web.php定义的路由规则 
  15.  return $route
  16.  
  17. /** 
  18.  * Add the given route to the arrays of routes. 
  19.  * 
  20.  * @param \Illuminate\Routing\Route $route 
  21.  * @return void 
  22.  */ 
  23. protected function addToCollections($route
  24.  $domainAndUri = $route->getDomain().$route->uri(); 
  25.  // dump($route->getDomain(), $route->uri()); null routecontroller 
  26.  foreach ($route->methods() as $method) { 
  27.   // 将路由规则挂载到数组 方便匹配 
  28.   $this->routes[$method][$domainAndUri] = $route
  29.  } 
  30.  // 将路由规则挂载的数组 方便匹配 
  31.  $this->allRoutes[$method.$domainAndUri] = $route

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

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

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

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

  1. public function match(Request $request
  2. {  
  3.  // 获取符合当前请求动作的所有路由 
  4.  // 是一个Route对象数组 每一个对象对应一个route规则 
  5.  $routes = $this->get($request->getMethod()); 
  6.    
  7.  // 匹配到当前请求路由 
  8.  $route = $this->matchAgainstRoutes($routes$request); 
  9.    
  10.  if (! is_null($route)) { 
  11.   // 将绑定了请求的Route实例返回 
  12.   return $route->bind($request); 
  13.  } 
  14.    
  15.  $others = $this->checkForAlternateVerbs($request); 
  16.  
  17.  if (count($others) > 0) { 
  18.   return $this->getRouteForMethods($request$others); 
  19.  } 
  20.  
  21.  throw new NotFoundHttpException; 
  22.  
  23. // 该方法中大量使用了collect方法 请查看laravel手册 
  24. protected function matchAgainstRoutes(array $routes$request$includingMethod = true) 
  25. {  
  26.  // dump(get_class_methods(get_class(collect($routes)))); 
  27.  // dump(collect($routes)->all()); // items数组 protected属性 
  28.  // dump(collect($routes)->items); // items属性是一个数组 
  29.    
  30.  // 当注册一个兜底路由的时候 (通过Route::fallback方法)对应$route的isFallback会被设为true 
  31.    
  32.  // partition方法根据传入的闭包将集合分成两部分 
  33.  // 具体实现可以查看手册 集合部分 
  34.  [$fallbacks$routes] = collect($routes)->partition(function ($route) { 
  35.   return $route->isFallback; 
  36.  }); 
  37.    
  38.  // 将兜底路由放到集合后面 并且通过first方法找到第一个匹配的路由 
  39.  return $routes->merge($fallbacks)->first(function ($valueuse ($request$includingMethod) { 
  40.   return $value->matches($request$includingMethod); 
  41.  }); 

Router文件

  1. protected function findRoute($request
  2. {  
  3.  // 可以打印$route 你会发现和你在web.php中打印的是同一个Route对象 
  4.  $this->current = $route = $this->routes->match($request); 
  5.  // 将匹配到的路由实例挂载到容器 
  6.  $this->container->instance(Route::class$route); 
  7.  
  8.  return $route
  9.  
  10. public function dispatchToRoute(Request $request
  11. {  
  12.  // 跳转到runRoute方法 
  13.  return $this->runRoute($request$this->findRoute($request)); 
  14.  
  15. protected function runRoute(Request $request, Route $route
  16. {  
  17.  // 给request帮顶当前的route 可以使用$request->route()方法 获取route实例 
  18.  // 你也可以随时在你的业务代码中通过容器获得当前Route实例  
  19.  // app(Illuminate\Routing\Route::class) 
  20.  $request->setRouteResolver(function () use ($route) { 
  21.   return $route
  22.  }); 
  23.    
  24.  $this->events->dispatch(new RouteMatched($route$request)); 
  25.    
  26.  // 开始准备响应了 
  27.  return $this->prepareResponse($request
  28.          // 跳转到runRouteWithinStack方法 
  29.          $this->runRouteWithinStack($route$request
  30.          ); 
  31.  
  32. protected function runRouteWithinStack(Route $route, Request $request
  33.  $shouldSkipMiddleware = $this->container->bound('middleware.disable') && 
  34.   $this->container->make('middleware.disable') === true; 
  35.  
  36.  $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); 
  37.    
  38.  // 依旧是一个pipeline 我们跳转到$route->run方法 
  39.  return (new Pipeline($this->container)) 
  40.   ->send($request
  41.   ->through($middleware
  42.   ->then(function ($requestuse ($route) { 
  43.    return $this->prepareResponse( 
  44.       
  45.     $request$route->run() 
  46.    ); 
  47.   }); 

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

  1. public function run() 
  2.  $this->container = $this->container ?: new Container; 
  3.  
  4.  try { 
  5.   // 如果是一个控制器路由规则 
  6.   // 显然我们的此条路由是一个控制器路由 
  7.   if ($this->isControllerAction()) { 
  8.    // 将执行的结果返回给$route->run() 
  9.    // 跳回到上面的prepareResponse方法 
  10.    return $this->runController(); 
  11.   } 
  12.    
  13.   // 如果是一个闭包路由规则ControllerDispatcher 
  14.   return $this->runCallable(); 
  15.  } catch (HttpResponseException $e) { 
  16.   return $e->getResponse(); 
  17.  } 
  18.  
  19. /** 
  20.  * Run the route action and return the response. 
  21.  * 
  22.  * @return mixed 
  23.  * 
  24.  * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException 
  25.  */ 
  26. protected function runController() 
  27.  //  
  28.  return $this->controllerDispatcher()->dispatch( 
  29.   $this
  30.   // 通过容器解析当前路由控制器实例 
  31.   $this->getController(), 
  32.   // 获取当前路由控制器方法 
  33.   $this->getControllerMethod() 
  34.  ); 

Illuminate\Routing\ControllerDispatcher::dispatch方法

  1. /** 
  2.  * Dispatch a request to a given controller and method. 
  3.  * 
  4.  * @param \Illuminate\Routing\Route $route 
  5.  * @param mixed $controller 
  6.  * @param string $method 
  7.  * @return mixed 
  8.  */ 
  9. public function dispatch(Route $route$controller$method
  10.  $parameters = $this->resolveClassMethodDependencies( 
  11.   $route->parametersWithoutNulls(), $controller$method 
  12.  ); 
  13.  
  14.  if (method_exists($controller'callAction')) { 
  15.   // 执行基类控制器中的callAction方法并返回执行结果 
  16.   return $controller->callAction($method$parameters); 
  17.  } 
  18.    
  19.  return $controller->{$method}(...array_values($parameters)); 

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

  1. protected function runRouteWithinStack(Route $route, Request $request
  2.  $shouldSkipMiddleware = $this->container->bound('middleware.disable') && 
  3.   $this->container->make('middleware.disable') === true; 
  4.  
  5.  $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); 
  6.  
  7.  return (new Pipeline($this->container)) 
  8.   ->send($request
  9.   ->through($middleware
  10.   ->then(function ($requestuse ($route) { 
  11.    return $this->prepareResponse( 
  12.     // 返回到这里 然后执行prepareResponse方法 
  13.     $request$route->run() 
  14.    ); 
  15.   }); 
  16.  
  17. // 实际调用的是toResponse方法 
  18. // 注意这里的$response是直接从控制器中返回的任何东西 
  19. public static function toResponse($request$response
  20.  if ($response instanceof Responsable) { 
  21.   // 我们当然可以直接从控制器中返回一个实现了Responsable接口的实例 
  22.   $response = $response->toResponse($request); 
  23.  } 
  24.  
  25.  if ($response instanceof PsrResponseInterface) { 
  26.   // 什么??? laravel还支持psr7?? 当然了 后面会附上使用文档 
  27.   $response = (new HttpFoundationFactory)->createResponse($response); 
  28.  } elseif ($response instanceof Model && $response->wasRecentlyCreated) { 
  29.   // 知道为什么laravel允许直接返回一个模型了吗 
  30.   $response = new JsonResponse($response, 201); 
  31.  } elseif (! $response instanceof SymfonyResponse && 
  32.     // 知道laravel为什么允许你直接返回数组了吗 
  33.     ($response instanceof Arrayable || 
  34.     $response instanceof Jsonable || 
  35.     $response instanceof ArrayObject || 
  36.     $response instanceof JsonSerializable || 
  37.     is_array($response))) { 
  38.   $response = new JsonResponse($response); 
  39.  } elseif (! $response instanceof SymfonyResponse) { 
  40.   // 如果没匹配到 比如response是一个字符串,null等 直接生成响应类 
  41.   // 我们从laravel的Response构造方法开始梳理 
  42.   $response = new Response($response); 
  43.  } 
  44.  
  45.  if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) { 
  46.   $response->setNotModified(); 
  47.  } 
  48.    
  49.  return $response->prepare($request); 

首先我们来看直接生成laravel响应 Illuminate\Http\Response

继承了Symfony\Component\HttpFoundation\Response

  1. // Symfony\Component\HttpFoundation\Response 
  2. public function __construct($content = '', int $status = 200, array $headers = []) 
  3. {  
  4.  // 可以看到基本什么都没做 
  5.  $this->headers = new ResponseHeaderBag($headers); 
  6.  // 调用Illuminate\Http\Response的setContent方法 设置响应内容呗 
  7.  $this->setContent($content); 
  8.  $this->setStatusCode($status); 
  9.  $this->setProtocolVersion('1.0'); 
  10.  
  11. // Illuminate\Http\Response::setContent 
  12. public function setContent($content
  13.  $this->original = $content
  14.    
  15.  // shouldBeJson方法将实现了特定接口的response或者是一个array的response转换为 
  16.  // 并设置响应头 
  17.  if ($this->shouldBeJson($content)) { 
  18.   $this->header('Content-Type''application/json'); 
  19.  // morphToJson方法保证最终给此响应设置的响应内容为json串 
  20.   $content = $this->morphToJson($content); 
  21.  } 
  22.    
  23.  elseif ($content instanceof Renderable) { 
  24.   $content = $content->render(); 
  25.  } 
  26.    
  27.  // Symfony\Component\HttpFoundation\Response 如果最终设置的响应内容不是null或者字符串或者实现了__toString方法的类 那么跑出异常, 否则设置响应内容 
  28.  parent::setContent($content); 
  29.  
  30.  return $this
  31.  
  32. // Symfony\Component\HttpFoundation\Response::setContent方法 
  33. public function setContent($content
  34.  if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content'__toString'])) { 
  35.   // php官方建议不要使用gettype方法获取变量的类型 
  36.   throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content))); 
  37.  } 
  38.  // (string) 会触发__toString方法 如何对象允许的话 
  39.  $this->content = (string) $content
  40.  return $this

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

  1. /** 
  2.  * Prepares the Response before it is sent to the client. 
  3.  * 
  4.  * This method tweaks the Response to ensure that it is 
  5.  * compliant with RFC 2616. Most of the changes are based on 
  6.  * the Request that is "associated" with this Response. 
  7.  * 
  8.  * @return $this 
  9.  */ 
  10. // 总的来说就是设置各种响应头 注意此时并未发送响应 
  11. public function prepare(Request $request
  12. {  
  13.  $headers = $this->headers; 
  14.  // 如果是100 204 304系列的状态码 就删除响应数据 删除对应的数据头  
  15.  if ($this->isInformational() || $this->isEmpty()) { 
  16.   $this->setContent(null); 
  17.   $headers->remove('Content-Type'); 
  18.   $headers->remove('Content-Length'); 
  19.  } else { 
  20.   // Content-type based on the Request 
  21.   if (!$headers->has('Content-Type')) { 
  22.    $format = $request->getPreferredFormat(); 
  23.    if (null !== $format && $mimeType = $request->getMimeType($format)) { 
  24.     $headers->set('Content-Type'$mimeType); 
  25.    } 
  26.   } 
  27.  
  28.   // Fix Content-Type 
  29.   $charset = $this->charset ?: 'UTF-8'
  30.   if (!$headers->has('Content-Type')) { 
  31.    $headers->set('Content-Type''text/html; charset='.$charset); 
  32.   } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { 
  33.    // add the charset 
  34.    $headers->set('Content-Type'$headers->get('Content-Type').'; charset='.$charset); 
  35.   } 
  36.  
  37.   // Fix Content-Length 
  38.   if ($headers->has('Transfer-Encoding')) { 
  39.    $headers->remove('Content-Length'); 
  40.   } 
  41.  
  42.   if ($request->isMethod('HEAD')) { 
  43.    // cf. RFC2616 14.13 
  44.    $length = $headers->get('Content-Length'); 
  45.    $this->setContent(null); 
  46.    if ($length) { 
  47.     $headers->set('Content-Length'$length); 
  48.    } 
  49.   } 
  50.  } 
  51.  
  52.  // Fix protocol 
  53.  if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { 
  54.   $this->setProtocolVersion('1.1'); 
  55.  } 
  56.  
  57.  // Check if we need to send extra expire info headers 
  58.  if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) { 
  59.   $headers->set('pragma''no-cache'); 
  60.   $headers->set('expires', -1); 
  61.  } 
  62.  
  63.  $this->ensureIEOverSSLCompatibility($request); 
  64.  
  65.  if ($request->isSecure()) { 
  66.   foreach ($headers->getCookies() as $cookie) { 
  67.    $cookie->setSecureDefault(true); 
  68.   } 
  69.  } 
  70.  
  71.  return $this
  72.  
  73. // 至此我们的响应封装好了 等待发送给客户端 
  74. // 在发送之前 还要将响应逐步返回 
  75. // 值得注意的是 如果你给此路由设置了后置中间件 可能如下 
  76. public function handle($request, Closure $next
  77. {  
  78.  // 此时拿到的$response就是我们上面响应好了一切 准备发送的响应了 希望你能理解后置中间件的作用了 
  79.  $response = $next($request); 
  80.  // header方法位于ResponseTrait 
  81.  $response->header('Server''xy'); 
  82.  return $response

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

响应返回到Router::runRoute方法

再返回到Router::dispatchToRoute方法

再返回到Router::dispatch方法

再返回到Illuminate\Foundation\Http::sendRequestThroughRouter方法 (注意只要是通过了管道都要注意中间件的类型)

最终返回到index.php中

  1. $response = $kernel->handle( 
  2.  $request = Illuminate\Http\Request::capture() 
  3. ); 
  4.  
  5. $response->send(); 
  6.  
  7. $kernel->terminate($request$response); 

我们来看send方法 Symfony\Component\HttpFoundation\Response::send

  1. public function send() 
  2. {  
  3.  // 先发送响应头 
  4.  $this->sendHeaders(); 
  5.  // 再发送响应主体 
  6.  $this->sendContent(); 
  7.  
  8.  if (\function_exists('fastcgi_finish_request')) { 
  9.   fastcgi_finish_request(); 
  10.  } elseif (!\in_array(\PHP_SAPI, ['cli''phpdbg'], true)) { 
  11.   static::closeOutputBuffers(0, true); 
  12.  } 
  13.  
  14.  return $this
  15.  
  16. public function sendHeaders() 
  17.  // headers have already been sent by the developer 
  18.  if (headers_sent()) { 
  19.   return $this
  20.  } 
  21.  
  22.  // headers 
  23.  foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { 
  24.   $replace = 0 === strcasecmp($name'Content-Type'); 
  25.   foreach ($values as $value) { 
  26.    // 将之前设置的各种头发送出去 
  27.    header($name.': '.$value$replace$this->statusCode); 
  28.   } 
  29.  } 
  30.  
  31.  // cookies 
  32.  foreach ($this->headers->getCookies() as $cookie) { 
  33.   // 告诉客户端要设置的cookie 
  34.   header('Set-Cookie: '.$cookie, false, $this->statusCode); 
  35.  } 
  36.  
  37.  // status 
  38.  // 最后发送个status 
  39.  header(sprintf('HTTP/%s %s %s'$this->version, $this->statusCode, $this->statusText), true, $this->statusCode); 
  40.  
  41.  return $this
  42.  
  43. // 发送响应内容  
  44. public function sendContent() 
  45. {  
  46.  // 想笑吗 就是这么简单 
  47.  echo $this->content; 
  48.  
  49.  return $this
  50. // 至此真的响应了客户端了 
  51. $kernel->terminate($request$response); 

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

  1. /** 
  2.  * Call the terminate method on any terminable middleware. 
  3.  * 
  4.  * @param \Illuminate\Http\Request $request 
  5.  * @param \Illuminate\Http\Response $response 
  6.  * @return void 
  7.  */ 
  8. public function terminate($request$response
  9. {  
  10.  // 调用实现了terminate方法的中间件 
  11.  $this->terminateMiddleware($request$response); 
  12.  // 执行注册的callback 
  13.  $this->app->terminate(); 

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

  1. public static function toResponse($request$response
  2.  if ($response instanceof Responsable) { 
  3.   $response = $response->toResponse($request); 
  4.  } 
  5.  
  6.  if ($response instanceof PsrResponseInterface) { 
  7.   $response = (new HttpFoundationFactory)->createResponse($response); 
  8.  } elseif ($response instanceof Model && $response->wasRecentlyCreated) { 
  9.   $response = new JsonResponse($response, 201); 
  10.  } elseif (! $response instanceof SymfonyResponse && 
  11.     ($response instanceof Arrayable || 
  12.     $response instanceof Jsonable || 
  13.     $response instanceof ArrayObject || 
  14.     $response instanceof JsonSerializable || 
  15.     is_array($response))) { 
  16.   $response = new JsonResponse($response); 
  17.  } elseif (! $response instanceof SymfonyResponse) { 
  18.   $response = new Response($response); 
  19.  } 
  20.  
  21.  if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) { 
  22.   $response->setNotModified(); 
  23.  } 
  24.  
  25.  return $response->prepare($request); 

观察上面的代码发现:

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

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

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

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

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

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

  1. <?php 
  2.  
  3. namespace Illuminate\Contracts\Support; 
  4.  
  5. interface Responsable 
  6.  /** 
  7.   * Create an HTTP response that represents the object. 
  8.   * 
  9.   * @param \Illuminate\Http\Request $request 
  10.   * @return \Symfony\Component\HttpFoundation\Response 
  11.   */ 
  12.  // 接收$request参数 
  13.  // 返回Response对象 
  14.  public function toResponse($request); 

下面我们在控制器中返回一个实现此接口的实例

要实现的逻辑: 接收一个订单id 根据订单状态生成不同的响应,返回给客户端

1 定义路由

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

2 创建响应

  1. namespace App\Responses; 
  2.  
  3. use App\Models\Order; 
  4. use Illuminate\Contracts\Support\Responsable; 
  5. use Illuminate\Http\JsonResponse; 
  6.  
  7. class OrderStatusRes implements Responsable 
  8.  protected $status
  9.  
  10.  public function __construct(Order $order
  11.  { 
  12.   $this->status = $order->status; 
  13.  } 
  14.  
  15.  public function toResponse($request
  16.  { 
  17.   if ($this->status) { 
  18.    // 订单以完成 
  19.    return new JsonResponse('order completed', 200); 
  20.   } 
  21.   // 订单未结算 
  22.   return view('needToCharge'); 
  23.  } 

3 创建控制器

  1. <?php 
  2.  
  3. namespace App\Http\Controllers\Debug; 
  4.  
  5. use App\Http\Controllers\Controller; 
  6. use App\Models\Order; 
  7. use App\Responses\OrderStatusRes; 
  8.  
  9. class TestController extends Controller 
  10.  public function checkStatus(Order $order
  11.  { 
  12.   return new OrderStatusRes($order); 
  13.  } 
  14.  
  15. // 进行访问测试 
  16. // http://homestead.test/yylh/1 
  17. // http://homestead.test/yylh/2 
可以看到丧心病狂的我们 通过控制器中的一行代码 就实现了根据订单的不同状态回复了不同的响应。

我想说什么你们应该已经知道了。

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

  1. Route::get('rawReponse'function () { 
  2.  
  3. ​ return new Response(range(1,10)); 
  4.  
  5. }); 
更多请查看这位老哥的博客

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

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

Tags: Reponse Laravel响应客户端

分享到: