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 相关文章推荐
smarty+adodb+部分自定义类的php开发模式
Dec 31 PHP
用php过滤危险html代码的函数
Jul 22 PHP
常用的php对象类型判断
Aug 27 PHP
php与java通过socket通信的实现代码
Oct 21 PHP
php使用pack处理二进制文件的方法
Jul 03 PHP
mysql_connect localhost和127.0.0.1的区别(网络层阐述)
Mar 26 PHP
php实现简单的MVC框架实例
Sep 23 PHP
Yii2超好用的日期和时间组件(值得收藏)
May 05 PHP
thinkPHP显示不出验证码的原因与解决方法分析
May 20 PHP
Laravel接收前端ajax传来的数据的实例代码
Jul 20 PHP
php 判断IP为有效IP地址的方法
Jan 28 PHP
PHP实现APP微信支付的实例讲解
Feb 10 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
PHP下MAIL的另一解决方案
2006/10/09 PHP
ThinkPHP实例化模型的四种方法概述
2014/08/22 PHP
PHP调试的强悍利器之PHPDBG
2016/02/22 PHP
php面向对象的用户登录身份验证
2017/06/08 PHP
php+websocket 实现的聊天室功能详解
2020/05/27 PHP
javascript 冒泡排序 正序和倒序实现代码
2010/12/14 Javascript
Three.js源码阅读笔记(物体是如何组织的)
2012/12/27 Javascript
JS刷新框架外页面七种实现代码
2013/02/18 Javascript
js实现简单鼠标跟随效果的方法
2015/04/10 Javascript
jQuery的文档处理程序详解
2016/05/10 Javascript
jQuery简单倒计时效果完整示例
2016/09/20 Javascript
Js自定义多选框效果的实例代码
2017/07/05 Javascript
jquery如何实现点击空白处隐藏元素
2017/12/05 jQuery
解决node.js含有%百分号时发送get请求时浏览器地址自动编码的问题
2019/11/20 Javascript
JS实现网站吸顶条
2020/01/08 Javascript
在Python的Django框架中包装视图函数
2015/07/20 Python
详解Python各大聊天系统的屏蔽脏话功能原理
2016/12/01 Python
利用Tkinter(python3.6)实现一个简单计算器
2017/12/21 Python
Window10+Python3.5安装opencv的教程推荐
2018/04/02 Python
Python之批量创建文件的实例讲解
2018/05/10 Python
django 删除数据库表后重新同步的方法
2018/05/27 Python
Python3从零开始搭建一个语音对话机器人的实现
2019/08/23 Python
django框架创建应用操作示例
2019/09/26 Python
PyCharm取消波浪线、下划线和中划线的实现
2020/03/03 Python
利用Python批量识别电子账单数据的方法
2021/02/08 Python
HTML5移动端手机网站开发流程
2016/04/25 HTML / CSS
新秀丽拉杆箱美国官方网站:Samsonite美国
2016/07/25 全球购物
国外平面设计第一市场:99designs
2016/10/25 全球购物
领导干部考察材料
2014/02/08 职场文书
《生命的药方》教学反思
2014/04/08 职场文书
党员承诺书格式
2014/05/21 职场文书
介绍信怎么写
2015/01/30 职场文书
2015年信息宣传工作总结
2015/05/26 职场文书
暑期工社会实践报告
2015/07/13 职场文书
让人感觉高大上的讲话稿怎么写?
2019/07/08 职场文书
Java基础——Map集合
2022/04/01 Java/Android