前言
本篇文章邏輯較長,只說明和響應生命周期相關的必要代碼。
本文主要內容順序為:
1、執(zhí)行上文管道中的then方法指定的閉包,路由的分發(fā)
2、在路由器中(Router類)找到請求($request 也就是經過全局中間件處理的請求)匹配的路由規(guī)則
3、說明路由規(guī)則的加載(會跳轉到框架的boot過程),注意這部分是在處理請求之前完成的,因為一旦當我們開始處理請求,就意味著所有的路由都應該已經加載好了,供我們的請求進行匹配
4、執(zhí)行請求匹配到的路由邏輯
5、生成響應,并發(fā)送給客戶端
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)
// 此方法將當前請求掛載到容器,然后執(zhí)行路由器的分發(fā)
->then($this->dispatchToRouter());
}
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
查看Illuminate\Routing\Router::dispatch方法
public function dispatch(Request $request)
{
$this->currentRequest = $request;
// 將請求分發(fā)到路由
// 跳轉到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());
// 匹配路由 下面查看框架如何生成的路由規(guī)則!!!
$route = $this->matchAgainstRoutes($routes, $request);
if (! is_null($route)) {
return $route->bind($request);
}
$others = $this->checkForAlternateVerbs($request);
if (count($others) > 0) {
return $this->getRouteForMethods($request, $others);
}
throw new NotFoundHttpException;
}
下面說明框架如何加載的路由規(guī)則
Application::boot方法
// 主要邏輯是調用服務提供者的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方法 觸發(fā)__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等路由文件中繼續(xù)require其他文件
// 便可實現(xiàn)不同功能模塊的路由管理
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"
// ]
}
// 創(chuàng)建一個對應路由規(guī)則的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定義的路由規(guī)則
return $route;
}
/**
* Add the given route to the arrays of routes.
*
* @param \Illuminate\Routing\Route $route
* @return void
*/
protected function addToCollections($route)
{
$domainAndUri = $route->getDomain().$route->uri();
// dump($route->getDomain(), $route->uri()); null routecontroller
foreach ($route->methods() as $method) {
// 將路由規(guī)則掛載到數組 方便匹配
$this->routes[$method][$domainAndUri] = $route;
}
// 將路由規(guī)則掛載的數組 方便匹配
$this->allRoutes[$method.$domainAndUri] = $route;
}
至此就生成了一條路由 注意我這里將注冊api路由進行了注釋,并且保證web.php中只有一條路由規(guī)則
以上是路由的加載 這部分是在$this->bootstrap()方法中完成的,還遠沒有到達路由分發(fā)和匹配的階段,希望大家能夠理解,至此路由規(guī)則生成完畢 保存到了RouteCollection實例中,每個路由規(guī)則都是一個Route對象,供請求進行匹配
下面根據此條路由進行匹配,并執(zhí)行返回結果
我們回到Illuminate\Routing\RouteCollection::match方法
public function match(Request $request)
{
// 獲取符合當前請求動作的所有路由
// 是一個Route對象數組 每一個對象對應一個route規(guī)則
$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方法根據傳入的閉包將集合分成兩部分
// 具體實現(xiàn)可以查看手冊 集合部分
[$fallbacks, $routes] = collect($routes)->partition(function ($route) {
return $route->isFallback;
});
// 將兜底路由放到集合后面 并且通過first方法找到第一個匹配的路由
return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) {
return $value->matches($request, $includingMethod);
});
}
Router文件
protected function findRoute($request)
{
// 可以打印$route 你會發(fā)現(xiàn)和你在web.php中打印的是同一個Route對象
$this->current = $route = $this->routes->match($request);
// 將匹配到的路由實例掛載到容器
$this->container->instance(Route::class, $route);
return $route;
}
public function dispatchToRoute(Request $request)
{
// 跳轉到runRoute方法
return $this->runRoute($request, $this->findRoute($request));
}
protected function runRoute(Request $request, Route $route)
{
// 給request幫頂當前的route 可以使用$request->route()方法 獲取route實例
// 你也可以隨時在你的業(yè)務代碼中通過容器獲得當前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 {
// 如果是一個控制器路由規(guī)則
// 顯然我們的此條路由是一個控制器路由
if ($this->isControllerAction()) {
// 將執(zhí)行的結果返回給$route->run()
// 跳回到上面的prepareResponse方法
return $this->runController();
}
// 如果是一個閉包路由規(guī)則ControllerDispatcher
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
/**
* Run the route action and return the response.
*
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function runController()
{
//
return $this->controllerDispatcher()->dispatch(
$this,
// 通過容器解析當前路由控制器實例
$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')) {
// 執(zhí)行基類控制器中的callAction方法并返回執(zhí)行結果
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(
// 返回到這里 然后執(zhí)行prepareResponse方法
$request, $route->run()
);
});
}
// 實際調用的是toResponse方法
// 注意這里的$response是直接從控制器中返回的任何東西
public static function toResponse($request, $response)
{
if ($response instanceof Responsable) {
// 我們當然可以直接從控制器中返回一個實現(xiàn)了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方法將實現(xiàn)了特定接口的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或者字符串或者實現(xiàn)了__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) 會觸發(fā)__toString方法 如何對象允許的話
$this->content = (string) $content;
return $this;
}
拿到響應后執(zhí)行return $response->prepare($request);
/**
* Prepares the Response before it is sent to the client.
*
* This method tweaks the Response to ensure that it is
* compliant with RFC 2616. Most of the changes are based on
* the Request that is "associated" with this Response.
*
* @return $this
*/
// 總的來說就是設置各種響應頭 注意此時并未發(fā)送響應
public function prepare(Request $request)
{
$headers = $this->headers;
// 如果是100 204 304系列的狀態(tài)碼 就刪除響應數據 刪除對應的數據頭
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;
}
// 至此我們的響應封裝好了 等待發(fā)送給客戶端
// 在發(fā)送之前 還要將響應逐步返回
// 值得注意的是 如果你給此路由設置了后置中間件 可能如下
public function handle($request, Closure $next)
{
// 此時拿到的$response就是我們上面響應好了一切 準備發(fā)送的響應了 希望你能理解后置中間件的作用了
$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()
{
// 先發(fā)送響應頭
$this->sendHeaders();
// 再發(fā)送響應主體
$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) {
// 將之前設置的各種頭發(fā)送出去
header($name.': '.$value, $replace, $this->statusCode);
}
}
// cookies
foreach ($this->headers->getCookies() as $cookie) {
// 告訴客戶端要設置的cookie
header('Set-Cookie: '.$cookie, false, $this->statusCode);
}
// status
// 最后發(fā)送個status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
return $this;
}
// 發(fā)送響應內容
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)
{
// 調用實現(xiàn)了terminate方法的中間件
$this->terminateMiddleware($request, $response);
// 執(zhí)行注冊的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);
}
觀察上面的代碼發(fā)現(xiàn):
1 上面代碼的作用是將路由節(jié)點返回的數據封裝成Response對象等待發(fā)送
2 并且上面的代碼存在大量的instanceof判斷 (為什么要這樣呢 是因為一旦我們從控制器中返回一個實現(xiàn)了
laravel指定接口的實例,laravel就知道該如何渲染這些響應給客戶端 此時你可能還不清楚,請看下面的例子)
3 而且沒有else分支(這是因為laravel允許我們直接返回reponse對象,當我們直接返回Resposne實例的時候會直接走到方法的最后一句話)
4 并且最終都調用的都是Symfony Response的prepare方法
我們先來看Responsable接口 在laravel中任何一個實現(xiàn)了此接口的對象 都可以響應給客戶端
?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);
}
// 下面我們在控制器中返回一個實現(xiàn)此接口的實例
// 要實現(xiàn)的邏輯: 接收一個訂單id 根據訂單狀態(tài)生成不同的響應,返回給客戶端
1 定義路由
Route::get('yylh/{order}', "\App\Http\Controllers\Debug\TestController@checkStatus");
2 創(chuàng)建響應
namespace App\Responses;
use App\Models\Order;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\JsonResponse;
class OrderStatusRes implements Responsable
{
protected $status;
public function __construct(Order $order)
{
$this->status = $order->status;
}
public function toResponse($request)
{
if ($this->status) {
// 訂單以完成
return new JsonResponse('order completed', 200);
}
// 訂單未結算
return view('needToCharge');
}
}
3 創(chuàng)建控制器
?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
// 可以看到喪心病狂的我們 通過控制器中的一行代碼 就實現(xiàn)了根據訂單的不同狀態(tài)回復了不同的響應
// 我想說什么你們應該已經知道了
看toResponse代碼 我們發(fā)現(xiàn) 只要我們想辦法返回符合laravel規(guī)定的數據,最終都會被轉換成laravel response實例 比如我們可以返回Responsable實例,Arrayable實例,Jsonable實例等等,大家可以嘗試直接返回return new Response(),Response::create等等
Route::get('rawReponse', function () {
return new Response(range(1,10));
});
更多請查看這位老哥的博客
通過十篇水文,分享了從類的自動加載,到走完laravel的生命周期。
第一次寫博客不足太多,不愛看大量代碼的道友,可以查看這位外國老哥的博客,其代碼較少,但邏輯清晰明了。發(fā)現(xiàn)錯誤,歡迎指導,感謝?。?!
collection文檔
laravel中使用psr7
總結
到此這篇關于Laravel Reponse響應客戶端的文章就介紹到這了,更多相關Laravel Reponse響應客戶端內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!