Laravel Reponse响应客户端示例详解


Posted in PHP onSeptember 03, 2020

前言

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

本文主要内容顺序为:

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

观察上面的代码发现:

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

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

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

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

4 并且最终都调用的都是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);
 }
}

// 进行访问测试
// 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

总结

到此这篇关于Laravel Reponse响应客户端的文章就介绍到这了,更多相关Laravel Reponse响应客户端内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP个人网站架设连环讲(四)
Oct 09 PHP
smarty实例教程
Nov 19 PHP
支持php4、php5的mysql数据库操作类
Jan 10 PHP
php输出xml格式字符串(用的这个)
Jul 12 PHP
PHP遍历目录并返回统计目录大小
Jun 09 PHP
php清空(删除)指定目录下的文件,不删除目录文件夹的实现代码
Sep 04 PHP
php简单实现查询数据库返回json数据
Apr 16 PHP
PHP会话处理的10个函数
Aug 11 PHP
PHP递归删除多维数组中的某个值
Apr 17 PHP
PHP编程获取音频文件时长的方法【基于getid3类】
Apr 20 PHP
PHP微信模板消息操作示例
Jun 29 PHP
基于php数组中的索引数组和关联数组详解
Mar 12 PHP
PHP 实现base64编码文件上传出现问题详解
Sep 01 #PHP
PHP copy函数使用案例代码解析
Sep 01 #PHP
PHP超全局变量实现原理及代码解析
Sep 01 #PHP
PHP终止脚本运行三种实现方法详解
Sep 01 #PHP
PHP如何使用array_unshift()在数组开头插入元素
Sep 01 #PHP
PHP数组Key强制类型转换实现原理解析
Sep 01 #PHP
Laravel中GraphQL接口请求频率实战记录
Sep 01 #PHP
You might like
Zend Framework入门之环境配置及第一个Hello World示例(附demo源码下载)
2016/03/21 PHP
PHP性能优化大全(php.ini)
2016/05/20 PHP
php图形jpgraph操作实例分析
2017/02/22 PHP
老生常谈ThinkPHP中的行为扩展和插件(推荐)
2017/05/05 PHP
PHP get_html_translation_table()函数用法讲解
2019/02/16 PHP
你真的了解JavaScript吗?
2007/02/24 Javascript
js常用排序实现代码
2010/12/28 Javascript
最新的10款jQuery内容滑块插件分享
2011/09/18 Javascript
jQuery获取样式中的背景颜色属性值/颜色值
2012/12/17 Javascript
JavaScript正则表达式中的ignoreCase属性使用详解
2015/06/16 Javascript
基于BootStrap栅格栏系统完成网站底部版权信息区
2016/12/23 Javascript
jQuery表单验证之密码确认
2017/05/22 jQuery
Angular2 父子组件通信方式的示例
2018/01/29 Javascript
利用Promise自定义一个GET请求的函数示例代码
2019/03/20 Javascript
Python-基础-入门 简介
2014/08/09 Python
python求众数问题实例
2014/09/26 Python
python中os操作文件及文件路径实例汇总
2015/01/15 Python
Python自动化运维和部署项目工具Fabric使用实例
2016/09/18 Python
Python实现GUI学生信息管理系统
2020/04/05 Python
Python中实现变量赋值传递时的引用和拷贝方法
2018/04/29 Python
python读取各种文件数据方法解析
2018/12/29 Python
python实现桌面壁纸切换功能
2019/01/21 Python
对Python3 pyc 文件的使用详解
2019/02/16 Python
python中的反斜杠问题深入讲解
2019/08/12 Python
树莓派4B+opencv4+python 打开摄像头的实现方法
2019/10/18 Python
python 在sql语句中使用%s,%d,%f说明
2020/06/06 Python
使用matplotlib的pyplot模块绘图的实现示例
2020/07/12 Python
Booking.com英国官网:全球酒店在线预订网站
2018/04/21 全球购物
伦敦一卡通:The London Pass
2018/11/30 全球购物
保安自我鉴定范文
2013/12/08 职场文书
工程负责人任命书
2014/06/06 职场文书
车辆年检委托书范本
2014/10/14 职场文书
《爱的教育》读书心得
2014/11/08 职场文书
卫生院艾滋病宣传活动总结
2015/05/09 职场文书
给校长的建议书作文300字
2015/09/14 职场文书
win10壁纸在哪个文件夹 win10桌面背景图片文件位置分享
2022/08/05 数码科技