laravel请求生命周期
引言
使用laravel框架已经有段日子了,这几天一直在研究框架源码,看了一些关于laravel生命周期的文章,觉得得自己总结一下才能理解更深刻。话不多说,本次研究使用的是laravel6.0的源码,laravel5.x貌似差不多,函数细节有不同,大体流程没变。
请求流程
- web服务器(Apache、Nginx等)通过配置文件定向到入口文件
public/index.php
- 加载composer的
autoload.php
文件 - 创建laravel应用实例(
Illuminate\Foundation\Application
),它是一个IoC容器(后面我们称之为服务容器),把laravel各个模块绑定在一起 - 服务容器绑定3个核心接口,Http核心接口
Illuminate\Contracts\Http\Kernel
、命令行核心接口Illuminate\Contracts\Console\Kernel
和异常处理接口Illuminate\Contracts\Debug\ExceptionHandler
- 服务容器调用make方法创建http核心类
App\Http\Kernel
的实例,它是Illuminate\Foundation\Http\Kernel
的子类,并且实现了Illuminate\Contracts\Http\Kernel
接口 Illuminate\Http\Request
通过capture
创建request
对象- http核心类实例的通过
handle
方法处理request
对象 - 上步的
handle
方法返回\Illuminate\Http\Response
的对象response
,response
对象调用send
发放返回结果给客户端 - 执行终止程序
服务容器创建过程
对应上面流程的第3步,使用new
创建Illuminate\Foundation\Application
实例,它继承自Illuminate\Container\Container
,而Container实现了Illuminate\Contracts\Container\Container
接口,主要源码如下
# bootstrap/app.php
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
# Illuminate\Foundation\Application
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
// 绑定服务容器实例,Illuminate\Foundation\Mix和
// Illuminate\Foundation\PackageManifest实例
$this->registerBaseBindings();
// 注册事件、日志和路由服务提供者
$this->registerBaseServiceProviders();
// 设置核心类的别名,最终把别名和类对应关系放在$aliases数组中,定义在Illuminate\Container\Container中
// $aliases是一维数组,key是类或接口全名,value是别名
// 多个类和接口可能对应同一个别名,这样做主要是框架中使用面向接口而实现的原则,传入的多是接口全名或父类全名,同一个别名保证可以获取我们想要的子类对象
$this->registerCoreContainerAliases();
}
# Illuminate\Foundation\Application
/**
* Register the basic bindings into the container.
*
* @return void
*/
protected function registerBaseBindings()
{
// 静态变量,定义在Illuminate\Container\Container
static::setInstance($this);
// instance方法绑定已有对象到容器
// 实际放在定义在Illuminate\Container\Container的$instances数组中,第一个参数为key,对象为value
// 绑定时设置为单例的对象会放在$instances数组中,后面需要时根据key直接返回
// 这个key是什么取决于instance方法第一个参数
$this->instance('app', $this); // 通过app别名获取服务容器实例
// 通过Illuminate\Container\Container类全名获取服务容器实例
$this->instance(Container::class, $this);
// 以单例形式绑定,后面创建对象会放在$instances数组
$this->singleton(Mix::class);
// 创建对象放在$instances数组,后面可以根据类全名访问到
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
# Illuminate\Foundation\Application
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
# Illuminate\Foundation\Application
/**
* Register a service provider with the application.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $force = false)
{
// 在已注册的服务提供者中需要,如果有则直接返回
// 这里$force参数的意思强制重新注册服务提供者
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// 如果$provider是字符串类型,通过new创建服务提供者,并且注入服务容器
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
// 执行服务提供者的register方法,有些register方法什么都没做,作为钩子预留在那,比如App\Providers\AppServiceProvider
// 注意服务提供者一共会有register方法,可能会有boot方法
$provider->register();
// 把服务提供者上绑定的服务绑定到容器上
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
// 有些服务以单例模式绑定的,绑定到容器时也要使用单例模式
// 其实singleton方法里面使用的就是bind方法,只不过最后一个$shared参数为true,表示将来创建的实例可以共享,即使用同一个实例
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
// 把服务提供者标记为已注册
// 实例放入$serviceProviders数组,定义在Illuminate\Foundation\Application中,前面getProvider方法就是从这个数组中获取的
$this->markAsRegistered($provider);
// 如果服务容器已启动,并且服务提供者存在boot方法则执行它的boot方法
// 注意在第3步时服务容器没有被启动($booted为false),直到第7步完成为http请求的引导程序后才启动,具体在\Illuminate\Foundation\Bootstrap\BootProviders
if ($this->isBooted()) {
$this->bootProvider($provider);
}
return $provider;
}
# Illuminate\Foundation\Application
/**
* Register the core class aliases in the container.
*
* @return void
*/
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],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::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],
'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);
}
}
}
第3步小结
个人理解new创建服务容器过程中主要有两点:1是服务容器绑定各种服务,服务提供者的register方法里也是绑定服务;2是建立类或接口的别名,方便后面使用。
服务容器绑定核心接口
# 代码位于bootstrap/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个接口,后面如果想创建某个接口类型的对象,实际创建的是第2个参数对应类的实例。singleton
方法其实调用的是bind
方法,代码如下
# Illuminate\Container\Container
/**
* 容器绑定的服务
*
* @var array[]
*/
protected $bindings = [];
/**
* 可共享的实例对象
*
* @var object[]
*/
protected $instances = [];
public function singleton($abstract, $concrete = null)
{
// 第3个参数true表示将来创建的对象是可共享的,对象存储在$instances数组中
$this->bind($abstract, $concrete, true);
}
// $abstract表示要绑定的抽象类型,可以是接口、抽象类或具体类
// $concrete表示将来创建$abstract类型对象时实际要实例化的类,如果$abstract已经是将来需要实例化的类,$concrete可以不传
// $shared表示将来创建的对象是否可以共享,单例方式绑定时参数为true,对象存储在$instances数组中
public function bind($abstract, $concrete = null, $shared = false)
{
// 释放旧的实例和别名
$this->dropStaleInstances($abstract);
// 没有传$concrete参数,表示$abstract即为将来实例化的类
if (is_null($concrete)) {
$concrete = $abstract;
}
// 如果$concrete不是Closure,会把它转换成Closure,Closure就是个闭包函数
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
// 绑定最主要的就是这步,把绑定关系存入$bindings数组,concrete通过为Closure,shared表示是否可共享
$this->bindings[$abstract] = compact('concrete', 'shared');
// 如果$abstract类型已经被解析,将会根据$abstract在$reboundCallbacks数组获取一组回调函数,然后依次执行
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
// 后面通过make方法创建对象时会用到这个闭包函数
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->resolve(
$concrete, $parameters, $raiseEvents = false
);
};
}
第4步小结
这步就是绑定了3个核心接口,绑定本质就是在$bindings数组中插入抽象对应的实际类,并指明是否可共享,结果如下,红框即为上面的3个接口
服务容器make方法创建http核心类
# public/index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
# Illuminate\Foundation\Application
public function make($abstract, array $parameters = [])
{
// 根据别名看$deferredServices数组中是否有延迟注册的服务提供者,有则注册,注册方法register在第3步介绍过
$this->loadDeferredProviderIfNeeded($abstract = $this->getAlias($abstract));
// 本质工作是在父类Illuminate\Container\Container的make方法中完成的
return parent::make($abstract, $parameters);
}
# Illuminate\Container\Container;
public function make($abstract, array $parameters = [])
{
// 搞了半天是调用resolve方法,这么写可能因为历史原因吧
return $this->resolve($abstract, $parameters);
}
# Illuminate\Container\Container
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
// 看是否构建上下文的实例
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// $instances存在该实例并且不需要创建上下文的实例,直接返回这个实例,单例的体现
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
// 获取抽象类型的具体类型,这里一般返回Closure,绑定时设置的
$concrete = $this->getConcrete($abstract);
// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract)) {
// 实例化$concrete,所有的依赖也都会被解析
$object = $this->build($concrete);
} else {
// 看到这里我是崩溃的,不过意思是清晰的,$concrete不能被实例化,那么就把它在来一遍解析
$object = $this->make($concrete);
}
// 查看$extenders数组中是否有闭包函数,有则依次执行这些函数,装饰该对象
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// 如果注册时设置可共享,对象放入$instances数组,后面流程直接取出使用,保证单例
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
// 执行回调函数,暂时没有深入研究
if ($raiseEvents) {
$this->fireResolvingCallbacks($abstract, $object);
}
// 设置为已解析
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}
# Illuminate\Container\Container
public function build($concrete)
{
// $concrete是闭包函数,执行这个函数
// 闭包定义的use中有实际的具体类,调用resolve解析它,最终还会走到这,此时$concrete为待实例化的具体类名
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
try {
// 要想真正能够实例话,$concrete必须为具体类的类名,不是就一直调用resolve解析,直到$abstract == $concrete
// 如果绑定时$abstract就是一个具体类,那么$abstract和$concrete都是这个类名,就直接build了,不用在resolve一次
// 创建该类的反射对象,关于ReflectionClass查手册就行了
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
// 如果这个类不能被实例化,比如你绑定搞了抽象类或接口,那么会抛出异常
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
// 当前正在创建的$concrete类型放入$buildStack数组,便于在不同方法中传递
$this->buildStack[] = $concrete;
// 获取类的构造函数
$constructor = $reflector->getConstructor();
// 如果没有构造函数,说明没有依赖,直接创建对象返回
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// 获取该类的所有依赖项
$dependencies = $constructor->getParameters();
// 如果依赖是类,则通过容器的make方法创建依赖的对象
try {
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
// 构建完成之前弹出$concrete类型,马上要完成了,不需要在$buildStack数组中了
array_pop($this->buildStack);
// 这里终于创建了对象,并且注入了依赖的对象,$instances是依赖对象的数组
return $reflector->newInstanceArgs($instances);
}
第5步小结
这里主要介绍了make方法,包含了容器创建对象的整个过程。原理是根据绑定时的信息,找到实际需要实例化的类名,通过反射机制最终完成实例化。这里体现了控制反转的思想,让对象的创建和依赖注入过程从程序员转向框架,同时服务容器和绑定的服务遵循了依赖反转原则,容器没有直接依赖具体服务类,而是容器和服务都依赖抽象(通常是接口),达到解耦的目的。
request对象的创建
# public/index/php
$request = Illuminate\Http\Request::capture()
# Illuminate\Http\Request
public static function capture()
{
static::enableHttpMethodParameterOverride();
// laravel的request对象从symfony request对象创建而来
return static::createFromBase(SymfonyRequest::createFromGlobals());
}
# Illuminate\Http\Request
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}
// 这里主要是把symfony request对象的属性值给到laravel的request对象
// 通过clone克隆了一个request对象副本,然后进行属性赋值
$newRequest = (new static)->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);
$newRequest->headers->replace($request->headers->all());
$newRequest->content = $request->content;
$newRequest->request = $newRequest->getInputSource();
return $newRequest;
}
# Symfony\Component\HttpFoundation\Request
public static function createFromGlobals()
{
// 把PHP全局变量封装到对象中
$request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);
if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
&& \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
}
return $request;
}
# Symfony\Component\HttpFoundation\Request
private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self
{
// 可以自定义回调方法创建request对象
if (self::$requestFactory) {
$request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content);
if (!$request instanceof self) {
throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
}
return $request;
}
return new static($query, $request, $attributes, $cookies, $files, $server, $content);
}
第6步小结
这步主要就是创建laravel的request对象,通过symfony request把$_GET
、$_POST
、$_COOKIE
、$_FILES
、$_SERVER
封装成对象属性,然后创建laravel的request对象,把symfony request的属性赋值给laravel的request对象相关属性,其实laravel的request是symfony request的子类,这些属性是从父类继承来的。简而言之,就是laravel把symfony request进行了封装改造。
处理请求
# public/index.php
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
# Illuminate\Foundation\Http\Kernel
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
# Illuminate\Foundation\Http\Kernel
protected function sendRequestThroughRouter($request)
{
// 将前面创建的request对象绑定到容器上,在生命周期内,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());
}
# Illuminate\Foundation\Http\Kernel
public function bootstrap()
{
// 没有被引导过,调用容器的bootstrapWith方法
if (! $this->app->hasBeenBootstrapped()) {
// $this->bootstrappers()方法返回一个数组,包含了需要完成引导的类
$this->app->bootstrapWith($this->bootstrappers());
}
}
// 需要引导的类
protected $bootstrappers = [
// 环境检测,通过 DOTENV 组件将 .env 配置文件载入到 $_ENV 变量中
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
// 将 config 目录下的所有配置文件读取到一个集合中,这样我们就可以项目里通过 config() 辅助函数获取配置数据。
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
// 异常处理
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
// 注册Facades,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
// 注册 Facades,注册完成后可以以别名的方式访问具体的类;
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
// 服务启动,注册服务提供者时服务容器已启动才会执行服务提供者的boot方法
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
# Illuminate\Foundation\Application
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
// $this['events']用法是因为Container实现了ArrayAccess接口
foreach ($bootstrappers as $bootstrapper) {
// 广播正在引导事件
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
// 获取引导类实例,调用实例的引导方法
$this->make($bootstrapper)->bootstrap($this);
// 广播正在引导结束事件
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
完成一系列引导后,下面看下请求是如何发给路由并处理的
# Illuminate\Foundation\Http\Kernel
protected function sendRequestThroughRouter($request)
{
// 将前面创建的request对象绑定到容器上,在生命周期内,request是单例
$this->app->instance('request', $request);
// 清楚之前的实例缓存
Facade::clearResolvedInstance('request');
// 执行引导程序,这里是重点
$this->bootstrap();
return (new Pipeline($this->app)) // 创建管道
->send($request) // 请求对象传入管道,赋值给$passable属性
// 把中间件传入管道,赋值给$pipes属性
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter()); // 依次执行中间件handle方法,都通过后找到路由执行请求
}
# Illuminate\Foundation\Http\Kernel
/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
# Illuminate\Routing\Router
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
# Illuminate\Routing\Router
/**
* 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));
}
# Illuminate\Routing\Router
/**
* Find the route matching a given request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
# Illuminate\Routing\Router
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
# Illuminate\Routing\Router
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
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(
$request, $route->run()
);
});
}
# Illuminate\Routing\Route
/**
* Run the route action and return the response.
* 执行路由行为,返回response
*
* @return mixed
*/
public function run()
{
$this->container = $this->container ?: new Container;
try {
// 如果是控制器,则实例化控制器,并执行对应方法
if ($this->isControllerAction()) {
return $this->runController();
}
// 如果是匿名函数则直接执行
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
# Illuminate\Pipeline\Pipeline
public function then(Closure $destination)
{
// 根据中间件数组通过回调函数迭代生成一个个闭包,闭包通过use包含了上一个闭包和中间件的信息
// array_reverse($this->pipes())是中间件数组,之所以要反转一下是为了下面先执行原数组的第一个,保证按照原数组定义的数组执行
// $this->carry()是用于闭包的回调函数
// 迭代的最终结果也是生成一个闭包,$this->prepareDestination($destination)是初始化的闭包
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
// 上面构造闭包是按照中间件反转的顺序构建的,则$pipeline是包含第一个中间件信息的闭包,会执行中间件类的handle方法
// 闭包中通过use包含了第二个中间件闭包,会传给第一个中间件handle方法的next参数,然后开始执行第二个中间件的闭包,进而执行第二个中间件的handle方法,依次下去
// 最终会调用构造时初始化的闭包,也就是上面的$this->prepareDestination($destination),返回最终结果
return $pipeline($this->passable);
}
# Illuminate\Pipeline\Pipeline
/**
* Get the final piece of the Closure onion.
*
* @param \Closure $destination
* @return \Closure
*/
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
}
# Illuminate\Pipeline\Pipeline
/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
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 (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
};
}
第7步小结
处理请求是通过http核心类的handle方法,主要包括完成一系列引导程序,通过管道将请求发送给路由,这过程中要经过中间件处理,中间件分为全局中间件和针对匹配路由的中间件。
返回请求结果给客户端
# Symfony\Component\HttpFoundation\Response
/**
* Sends HTTP headers and content.
*
* @return $this
*/
public function send()
{
$this->sendHeaders(); // 发送响应头部信息
$this->sendContent(); // 发送响应主体
if (\function_exists('fastcgi_finish_request')) {
// 冲刷所有响应数据给客户端,使得与客户端断开连接后,服务端依然能够执行比较耗时的任务,这要在php-fpm下才会执行
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
}
return $this;
}
# Symfony\Component\HttpFoundation\Response
/**
* Sends HTTP headers.
*
* @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) {
header('Set-Cookie: '.$cookie, false, $this->statusCode);
}
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
return $this;
}
# Symfony\Component\HttpFoundation\Response
/**
* Sends content for the current web response.
*
* @return $this
*/
public function sendContent()
{
echo $this->content;
return $this;
}
第8步小结
这步比较简单,就是把请求处理结果返回给客户端。
执行终止程序
# Illuminate\Foundation\Http\Kernel
/**
* 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);
// 执行服务容器的终止程序
$this->app->terminate();
}
# Illuminate\Foundation\Http\Kernel
/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
[$name] = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}
# Illuminate\Foundation\Application
/**
* Terminate the application.
*
* @return void
*/
public function terminate()
{
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}
第9步小结
这步就是执行终止程序,包括中间件终止和服务容器终止。