Laravel Reponse响应客户端示例详解

前言

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

本文主要内容顺序为:

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响应客户端内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Laravel Reponse响应客户端示例详解

    前言 本篇文章逻辑较长,只说明和响应生命周期相关的必要代码. 本文主要内容顺序为: 1.执行上文管道中的then方法指定的闭包,路由的分发 2.在路由器中(Router类)找到请求($request 也就是经过全局中间件处理的请求)匹配的路由规则 3.说明路由规则的加载(会跳转到框架的boot过程),注意这部分是在处理请求之前完成的,因为一旦当我们开始处理请求,就意味着所有的路由都应该已经加载好了,供我们的请求进行匹配 4.执行请求匹配到的路由逻辑 5.生成响应,并发送给客户端 6.最后生命周期

  • Laravel多用户认证系统示例详解

    前言 自从Laravel5.2开始,自带的Auth认证系统可以支持多个角色认证了.就是说你比如果有管理员.普通用户这两种角色,都可以通过同一个Auth系统来实现认证. 本文将详细给大家介绍关于Laravel多用户认证系统的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. #1 自动生成代码 Laravel自带的Auth可以通过一行命令来生成相关的认证控制器.模版以及路由: php artisan make:auth 这样就会生成一个AuthController认证控制器

  • 浅谈使用Java Web获取客户端真实IP的方法示例详解

    Java-Web获取客户端真实IP: 发生的场景:服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等情况,在进行客户端IP限定的时候,需要首先获取该真实的IP. 一般分为两种情况: 方式一.客户端未经过代理,直接访问服务器端(nginx,squid,haproxy): 方式二.客户端通过多级代理,最终到达服务器端(nginx,squid,haproxy): 客户端请求信息都包含在HttpServletRequest中,可以通过方法getRemoteAddr()获得该客户端IP.

  • Vue响应式原理的示例详解

    Vue 最独特的特性之一,是非侵入式的响应系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.聊到 Vue 响应式实现原理,众多开发者都知道实现的关键在于利用 Object.defineProperty , 但具体又是如何实现的呢,今天我们来一探究竟. 为了通俗易懂,我们还是从一个小的示例开始: <body> <div id="app"> {{ message }} </div> <script> v

  • vue3响应式Object代理对象的读取示例详解

    目录 正文 读取属性 xx in obj for ... in 正文 从这一章开始,作者将更新深入的讲解响应式,尤其是vue3响应式的具体的实现.其实在前面一章,如果你仔细阅读,你是可以实现一个简单的响应式函数的,类似于@vue/reactive,当然那只是个demo,是个玩具,我能不能在生产环境上去使用的,它差了太多功能和边界条件. 现在,我们才是真正的深入@vue/reactive. 在vue中,obj.a是一个读取操作,但是仔细想来,读取这个操作很宽泛. obj.a // 访问一个属性 '

  • 基于laravel Request的所有方法详解

    获取请求的实例 通过 Facade Request 这个 facade 可以让我们得到绑定在容器里的当前这个请求.比如: $name = Request::input('name'); 注意,如果你在一个命名空间里,你需要在类文件的顶部使用 use Request; 这条声明来导入 Request 这个 facade . 通过依赖注入 要通过依赖注入得到当前 HTTP 请求的实例,需要在你的控制器构造函数或者方法里 type-hint 类.当前请求的这个实例会被 Service Containe

  • Servlet开发JavaWeb工程示例详解

    一.什么是Servlet? Servlet是在服务器上运行的小程序,也就是一个Java类,但比较特殊,不需要new,自动就可以运行.也有创建.垃圾回收和销毁过程.Servlet是JavaWeb的三大组件之一(Servlet.Filter.Listener),它属于动态资源.Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要: 接收请求数据: 处理请求: 完成响应. 例如客户端发出登录请求,或者输出注册请求,这些请求都应该由Servlet来完

  • SpringBoot2 整合FreeMarker实现页面静态化示例详解

    一.页面静态化 1.动静态页面 静态页面 即静态网页,指已经装载好内容HTML页面,无需经过请求服务器数据和编译过程,直接加载到客户浏览器上显示出来.通俗的说就是生成独立的HTML页面,且不与服务器进行数据交互. 优缺点描述: 静态网页的内容稳定,页面加载速度极快: 不与服务器交互,提升安全性: 静态网页的交互性差,数据实时性很低: 维度成本高,生成很多HTML页面: 动态页面 指跟静态网页相对的一种网页编程技术,页面的内容需要请求服务器获取,在不考虑缓存的情况下,服务接口的数据变化,页面加载的

  • SpringBoot框架集成ElasticSearch实现过程示例详解

    目录 依赖 与SpringBoot集成 配置类 实体类 测试例子 RestHighLevelClient直接操作 索引操作 文档操作 检索操作 依赖 SpringBoot版本:2.4.2 <dependencies> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <opti

  • Gin与Mysql实现简单Restful风格API实战示例详解

    目录 It works main.go 编译运行 数据库 CURD 增删改查 增 查 查询列表 Query 查询单条记录 QueryRow 改 删 组织代码 封装模型方法 Handler函数 组织项目 数据库处理 数据model封装 handler 路由 分组路由 app入口 总结 我们已经了解了Golang的Gin框架.对于Webservice服务,restful风格几乎一统天下.Gin也天然的支持restful.下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全.我们先以一个单文件开始,然

随机推荐