Laravel框架启动流程分析
注:当笔者写本文时laravel的最新版本是9.0,因此本文基于laravel9.0版本编写
两个入口
分析启动,我们一般都从入口开始,laravel有两个入口文件,一个是基于php-fpm的public/index.php,一个是基于命令行的artisan文件。
运行前准备
我们先讲/public/index.php,后面再补充命令行的
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);
当一个请求过来的时候,laravel先判断站点是否处于维护模式(maintenance),如果是,则直接显示维护内容。如果不是,则启动composer自加载,自动加载composer库,然后开始laravel机器开始转动了
$app = require_once __DIR__.'/../bootstrap/app.php';
这里实例化一个对象app,我们看一下app,我们看一下app,我们看一下app
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
在这里,框架做了几件事。1、注册HTTP和Console内核,2、并将应用程序的实例返回到index.php。app.php在这里应该理解为一个工厂,这就是为什么它要返回app变量,尽管由于require,app变量,尽管由于require,app变量,尽管由于require,app是在全局上下文中。现在有一个Illuminate\Foundation\Application类,这个就是Laravel应用实例。这个类包含了从环境,Laravel版本,容器,路径等所有信息。Application继承子容器Container。Laravel所有的内核精华都在这两个类里面。
看一下Application类
class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
use Macroable;
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
$this->useLangPath(value(function () {
if (is_dir($directory = $this->resourcePath('lang'))) {
return $directory;
}
return $this->basePath('lang');
}));
}
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->singleton(PackageManifest::class, function () {
return new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
);
});
}
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
public function registerCoreContainerAliases()
{
foreach ([
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class],
'cache.psr6' => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'db.schema' => [\Illuminate\Database\Schema\Builder::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\StringEncrypter::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
'mail.manager' => [\Illuminate\Mail\MailManager::class, \Illuminate\Contracts\Mail\Factory::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'redis.connection' => [\Illuminate\Redis\Connections\Connection::class, \Illuminate\Contracts\Redis\Connection::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
}
从构造方法开始,Application做了很多事情,总结如下1、通过use Macroable实现了门面方法调用的扩展2、设置项目根路径basePath,通过bindPathsInContainer绑定各种路径到容器里面<br/>3、通过static::setInstance(basePath,通过bindPathsInContainer绑定各种路径到容器里面<br />3、通过static::setInstance(basePath,通过bindPathsInContainer绑定各种路径到容器里面<br/>3、通过static::setInstance(this);this−>instance(′app′,this->instance('app', this−>instance(′app′,this);this−>instance(Container::class,this->instance(Container::class, this−>instance(Container::class,this);将自己绑定到laravel容器里面,并关联到‘app',Container::class,ContainerInterface.class等别名4、注册Mix实例用于前端mix函数5、实例化PackageManifest 类到容器中用于收集composer信息的相关操作6、注册了3个核心的服务提供者,
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
通过EventServiceProvider和LogServiceProvider,将事件派发类Dispatcher和事件管理类LogManager实例化到容器中。RoutingServiceProvider则是提供了各种路由、url、psr、request、response等容器对象的绑定。7、通过registerCoreContainerAliases注册了众多框架运行必须的核心容器对象,并绑定到各自的别名上。,我们可以看到所有的服务,如认证管理器、邮件发送器、数据库,都已经加载。请注意,到这时我们还没有加载.env文件、配置,或者实例化数据库的连接。我们只是在容器中加载和存储类而已。
回到app.php
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
这里又绑定了3个内核,App\Http\Kernel::class用户处理http请求,App\Console\Kernel::class用于处理控制台请求,分别对应了文章开头说的两个入口,App\Exceptions\Handler::class用于捕获异常。至此,application运行启动前的所有环境准备完成,接下去就是框架从容器中拿对应的东西做对应的事情。
运行启动
主要代码
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
如果是基于php-fpm启动,那么Kernel::class就是对应App\Http\Kernel::class,而这个类又继承于Illuminate\Foundation\Http\Kernel,我们看一下Illuminate\Foundation\Http\Kernel。
class Kernel implements KernelContract
{
use InteractsWithTime;
protected $app;
protected $router;
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
protected $middleware = [];
protected $middlewareGroups = [];
protected $routeMiddleware = [];
protected $requestLifecycleDurationHandlers = [];
protected $requestStartedAt;
protected $middlewarePriority = [
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;
$this->syncMiddlewareToRouter();
}
protected function syncMiddlewareToRouter()
{
$this->router->middlewarePriority = $this->middlewarePriority;
foreach ($this->middlewareGroups as $key => $middleware) {
$this->router->middlewareGroup($key, $middleware);
}
foreach ($this->routeMiddleware as $key => $middleware) {
$this->router->aliasMiddleware($key, $middleware);
}
}
可以看到,已经预定义了一组优先级中间件middlewarePriority,框架把优先级中间件注入到router,紧接着注入App\Http\Kernel::class中定义的分组中间件和路由中间件。∗∗我们在使用laravel开发应用时这主要定义middlewarePriority,框架把优先级中间件注入到router,紧接着注入App\Http\Kernel::class中定义的分组中间件和路由中间件。**我们在使用laravel开发应用时这主要定义middlewarePriority,框架把优先级中间件注入到router,紧接着注入App\Http\Kernel::class中定义的分组中间件和路由中间件。∗∗我们在使用laravel开发应用时这主要定义middlewareGroups和routeMiddleware∗∗,routeMiddleware**,routeMiddleware∗∗,middlewarePriority一般很少东。
处理请求开始
$response = $kernel->handle(
$request = Request::capture()
)->send();
当执行到这里时,框架开始处理请求并响应请求我们看一下handle方法
public function handle($request)
{
$this->requestStartedAt = Carbon::now();
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
1、通过$request->enableHttpMethodParameterOverride();启动了对restful的_method方法的支持。2、紧接着,laravel将请求通过sendRequestThroughRouter来进行处理。
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());
}
$this->bootstrap();是重点,通过bootstrap,Laravel又做了很多事情
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,// 加载env环境变量
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,// 加载配置文件
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,// 异常处理
\Illuminate\Foundation\Bootstrap\RegisterFacades::class, // 注册 Facades
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,// 注册 Providers
\Illuminate\Foundation\Bootstrap\BootProviders::class,// 启动 Providers
];
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
其中RegisterProviders和BootProviders工作量最大,我们自定义的服务提供者都在这个时候触发生效了。bootstrappers执行完毕之后。Laravel开始通过管道(pipeline)进行请求处理。管道是个难点,这里你可以理解为循环使用中间件来处理请求。当所有的中间件处理完之后,交由 dispatchToRouter 进行下一步处理。
管道原理
管道使用了一个很重要的php内置函数array_reducearray_reduce(array array,[callable](https://www.php.net/manual/zh/language.types.callable.php)array, [callable](https://www.php.net/manual/zh/language.types.callable.php) array,[callable](https://www.php.net/manual/zh/language.types.callable.php)callback, mixed $initial = null): mixedarray_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。我们先手写一段简单代码类理解
$middlewares = ['middle1','middle2','middle3'];
$callback = function($aftermiddleware,$item){
return $aftermiddleware.$item.'-->';
};
$endback = 'controller->method-->';
$res = array_reduce($middlewares,$callback,$endback);
var_dump($res);
//输出controller->method-->middle1-->middle2-->middle3-->
我们看到本该最后执行的$endback却最先执行了,我们在改进一下,换成如下
$middlewares = ['middle1', 'middle2', 'middle3'];
$endback = function () {
echo 'controller->method';
};
$res = array_reduce($middlewares, function ($aftermiddleware, $item) {
return function() use($aftermiddleware,$item){
if($item instanceof \Closure){
$theitem = $item();
}
else{
$theitem = $item;
}
echo $theitem.'-->';
$aftermiddleware($theitem);
};
}, $endback);
$res();
//输出middle3-->middle2-->middle1-->controller->method
可以看到,通过闭包包装后,执行输出的顺便改变了,可是我们要的middle1到middle3的执行顺序也相反了,我们对middlewares做一下middlewares做一下middlewares做一下middlewares = array_reverse($middlewares);此时输出顺序就会变为middle1-->middle2-->middle3-->controller->method了,这就是管道原理
Laravel管道原理:使用array_reduce把所有要通过的中间件都通过 callback 方法并压缩为一个 Closure。最后在执行 Initial
我们回到laravel代码中Laravel中的主要管道算法如下
//Illuminate\Foundation\Http\Kernel.php
protected function sendRequestThroughRouter($request)
{
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);
};
}
public function send($passable)
{
$this->passable = $passable;
return $this;
}
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
public function pipe($pipes)
{
array_push($this->pipes, ...(is_array($pipes) ? $pipes : func_get_args()));
return $this;
}
public function via($method)
{
$this->method = $method;
return $this;
}
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
public function thenReturn()
{
return $this->then(function ($passable) {
return $passable;
});
}
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
}
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
if (is_callable($pipe)) {
// If the pipe is a callable, then we will call it directly, but otherwise we
// will resolve the pipes out of the dependency container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $this->handleCarry($carry);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
};
}
可以看到,laravel就是发送一个 $request 对象,通过 middleware 中间件数组,最后在执行 dispatchToRouter 方法的。假设有两个全局中间件,我们来看看这两个中间件是如何通过管道压缩为一个 Closure 的
Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
App\Http\Middleware\AllowOrigin::class,//自定义中间件
在 Illuminate\Pipeline\Pipeline 的 then 方法中,destination为代码中的的∗∗dispatchToRouter∗∗闭包,即初始值,pipes为要通过的中间件数组,passable为Request对象。arrayreverse函数将中间件数组的每一项都通过destination 为代码中的的 **dispatchToRouter** 闭包,即初始值,pipes 为要通过的中间件数组,passable 为 Request 对象。array_reverse 函数将中间件数组的每一项都通过 destination为代码中的的∗∗dispatchToRouter∗∗闭包,即初始值,pipes为要通过的中间件数组,passable为Request对象。arrayreverse函数将中间件数组的每一项都通过this->carry()。
- 第一次迭代时,返回一个闭包,use 了 stack和stack 和 stack和pipe,stack的值为初始值闭包,stack 的值为初始值闭包,stack的值为初始值闭包,pipe 为中间件类名,此处是 App\Http\Middleware\AllowOrigin::class(注意 array_reverse 函数把传进来的中间件数组倒叙了)。假设我们直接运行该闭包,由于此时 pipe是一个String类型的中间件类名,只满足!isobject(pipe 是一个 String 类型的中间件类名,只满足 ! is_object(pipe是一个String类型的中间件类名,只满足!isobject(pipe) 这个条件,我们将直接从容器中 make 一个该中间件的实列出来,在执行该中间件实列的 handle 方法(**默认 this−>method为handle∗∗)。并且将request对象和初始值作为参数,传给这个中间件。在这个中间件的handle方法中,当我们直接执行returnthis->method 为 handle**)。并且将 request 对象和初始值作为参数,传给这个中间件。在这个中间件的 handle 方法中,当我们直接执行 return this−>method为handle∗∗)。并且将request对象和初始值作为参数,传给这个中间件。在这个中间件的handle方法中,当我们直接执行returnnext($request) 时,相当于我们开始执行 array_reduce 函数的初始值闭包了,即上述的 dispatchToRouter 方法返回的闭包。好,假设结束。
这里的handle方法调用的就行我们平时写laravel中间件时的handle方法。
- 在第二次迭代时,也返回一个 use 了 stack和stack 和 stack和pipe,stack的值为我们第一次迭代时返回的闭包,stack 的值为我们第一次迭代时返回的闭包,stack的值为我们第一次迭代时返回的闭包,pipe 为中间件类名,此处是 Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class。
- 两次迭代结束,回到 then 方法中,我们手动执行了第二次迭代返回的闭包。当执行第二次迭代返回的闭包时,当前闭包 use 的 pipe为Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,同样只满足!isobject(pipe为 Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,同样只满足 ! is_object(pipe为Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,同样只满足!isobject(pipe) 这个条件,我们将会从容器中 make 出 CheckForMaintenanceMode 中间件的实列,在执行该实列的 handle 方法,并且把第一次迭代返回的闭包作为参数传到 handle 方法中。
- 当我们在 CheckForMaintenanceMode 中间件的 handle 方法中执行 return next(next(next(request) 时,此时的 next为我们第一次迭代返回的闭包,将回到我们刚才假设的流程那样。从容器中make一个App\Http\Middleware\AllowOrigin实列,在执行该实列的handle方法,并把初始值闭包作为参数传到AllowOrigin中间件的handle方法中。当我们再在AllowOrigin中间件中执行returnnext 为我们第一次迭代返回的闭包,将回到我们刚才假设的流程那样。从容器中 make 一个 App\Http\Middleware\AllowOrigin 实列,在执行该实列的 handle 方法,并把初始值闭包作为参数传到 AllowOrigin 中间件的 handle方法中。当我们再在 AllowOrigin 中间件中执行 return next为我们第一次迭代返回的闭包,将回到我们刚才假设的流程那样。从容器中make一个App\Http\Middleware\AllowOrigin实列,在执行该实列的handle方法,并把初始值闭包作为参数传到AllowOrigin中间件的handle方法中。当我们再在AllowOrigin中间件中执行returnnext($request) 时,代表我们所有中间件都通过完成了,接下来开始执行 dispatchToRouter。
总结
1、中间件是区分先后顺序的,从这里你应该能明白为什么要把中间件用 array_reverse 倒叙了。 2、并不是所有中间件在运行前都已经实例化了的,用到的时候才去想容器取 3、中间件不执行 next(next (next(request) 后续所有中间件无法执行。
响应开始
1、路由匹配
Laravel执行完管道环节后,如果中间件没有退出的话,那么最终会执行dispatchToRouter,在这里laravel会寻找路由匹配到最终执行的方法
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Dispatch the request to a route and return the response.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
/**
* Find the route matching a given request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->events->dispatch(new Routing($request));
$this->current = $route = $this->routes->match($request);
$route->setContainer($this->container);
$this->container->instance(Route::class, $route);
return $route;
}
在我们执行路由动作之前,我们必须找到与我们的请求URI相匹配的路由。在我们的Request对象上有requestUri和方法属性,可以帮助我们做到这一点。在findRoute方法中,我们从RouteCollection对象中调用匹配方法,找到路由,将其映射到路由对象中,完成后,我们将该路由对象绑定到容器中。请注意,RouteCollection是我们在注册核心RoutingServiceProvide时在构造函数中实例化的类。匹配路由
public function match(Request $request)
{
$routes = $this->get($request->getMethod());
// First, we will see if we can find a matching route for this current request
// method. If we can, great, we can just return it so that it can be called
// by the consumer. Otherwise we will check for routes with another verb.
$route = $this->matchAgainstRoutes($routes, $request);
return $this->handleMatchedRoute($request, $route);
}
protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
{
[$fallbacks, $routes] = collect($routes)->partition(function ($route) {
return $route->isFallback;
});
return $routes->merge($fallbacks)->first(
fn (Route $route) => $route->matches($request, $includingMethod)
);
}
public function matches(Request $request, $includingMethod = true)
{
$this->compileRoute();
foreach (self::getValidators() as $validator) {
if (! $includingMethod && $validator instanceof MethodValidator) {
continue;
}
if (! $validator->matches($this, $request)) {
return false;
}
}
return true;
}
这里的匹配方法解有点复杂,先通过多种验证器:URIValidator, SchemeValidator, HostValidator & MethodValidator来匹配特定路由。这些验证器类试图为其特定目的验证路由。如果找到了任何匹配的路由,laravel就将请求与路由绑定,并将其返回。然而,如果在这个过程中没有匹配的路由,laravel将循环浏览路由集合,使用checkForAlternateVerbs($request)查看该URI是否存在不同的HTTP方法。如果不存在,则抛回一个404异常
2、执行路由
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(fn () => $route);
$this->events->dispatch(new RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
注意,我们最初需要在Request类中设置路由解析器,所以当你在你的类中使用$request->route()方法时,该解析器会找到该路由并将其返回给你。之后,laravel发射了一个事件RouteMatched。然后通过runRouteWithinStack执行路由中间件
$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(fn ($request) => $this->prepareResponse(
$request, $route->run()
));
这里又来的一个管道模型,如何拆解和前面讲的一样。最终laravel吧处理结果通过prepareResponse包装成Response对象返回给入口文件index.php。
public function prepareResponse($request, $response)
{
return static::toResponse($request, $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 Stringable) {
$response = new Response($response->__toString(), 200, ['Content-Type' => 'text/html']);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
$response instanceof stdClass ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response, 200, ['Content-Type' => 'text/html']);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}
接着。通过$response->send();将信息返回给浏览器。
/**
* Sends HTTP headers and content.
*
* @return $this
*/
public function send(): static
{
$this->sendHeaders();
$this->sendContent();
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (\function_exists('litespeed_finish_request')) {
litespeed_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
}
return $this;
}
最后,通过kernel−>terminate(kernel->terminate(kernel−>terminate(request, $response);laravel执行了中间件的terminate方法,至此,整个请求流程结束。
转载自:https://juejin.cn/post/7239715562832986169