laravel请求生命周期

引言

使用laravel框架已经有段日子了,这几天一直在研究框架源码,看了一些关于laravel生命周期的文章,觉得得自己总结一下才能理解更深刻。话不多说,本次研究使用的是laravel6.0的源码,laravel5.x貌似差不多,函数细节有不同,大体流程没变。

请求流程

  1. web服务器(Apache、Nginx等)通过配置文件定向到入口文件public/index.php
  2. 加载composer的autoload.php文件
  3. 创建laravel应用实例(Illuminate\Foundation\Application),它是一个IoC容器(后面我们称之为服务容器),把laravel各个模块绑定在一起
  4. 服务容器绑定3个核心接口,Http核心接口Illuminate\Contracts\Http\Kernel、命令行核心接口Illuminate\Contracts\Console\Kernel和异常处理接口Illuminate\Contracts\Debug\ExceptionHandler
  5. 服务容器调用make方法创建http核心类App\Http\Kernel的实例,它是Illuminate\Foundation\Http\Kernel的子类,并且实现了Illuminate\Contracts\Http\Kernel接口
  6. Illuminate\Http\Request通过capture创建request对象
  7. http核心类实例的通过handle方法处理request对象
  8. 上步的handle方法返回\Illuminate\Http\Response的对象responseresponse对象调用send发放返回结果给客户端
  9. 执行终止程序

服务容器创建过程

对应上面流程的第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步小结

这步就是执行终止程序,包括中间件终止和服务容器终止。

参考

posted @ 2022-06-24 11:25  whyly  阅读(60)  评论(0编辑  收藏  举报