生命周期 4--handle 前期工作

已创建 $app, $kernel, $request 对象,接下来,终于可以开始处理request对象了(实际上是我想多了)。

kernel处理request(上)

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();  //1

            $response = $this->sendRequestThroughRouter($request);  //2
        } catch (Exception $e) {
            $this->reportException($e); //3

            $response = $this->renderException($request, $e);   //4
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));   //5

            $response = $this->renderException($request, $e);   //6
        }

        $this->app['events']->dispatch( 
            new Events\RequestHandled($request, $response)
        );  //7

        return $response;
    }

1 启动表单方法重载

  • 前面创建IlluminateRequest对象时已经设置过了,这里再设置一次,可能是出于保险考虑。

2 将$request通过路由和中间件过滤。

    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);  //2.1

        Facade::clearResolvedInstance('request');   //2.2

        $this->bootstrap(); //2.3

        return (new Pipeline($this->app))   //2.4
                    ->send($request)    //2.5
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)  //2.6
                    ->then($this->dispatchToRouter());  //2.7
    }

2.1 将$request对象绑定到service container中,绑定键为request.

2.2 清除一个已解析过的facade实例(目前还没有解析过的facade实例)

2.3 启动laravel应用

    /**
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {  
            $this->app->bootstrapWith($this->bootstrappers());  //2.3.1
        }
    }

2.3.1 如果laravel应用程序还没有启动,就以给定的bootstrappers来启动。

    /**
     * Run the given array of bootstrap classes.
     *
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;  //2.3.1.1

        foreach ($bootstrappers as $bootstrapper) { 
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); //2.3.1.2

            $this->make($bootstrapper)->bootstrap($this); //2.3.1.3

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); //2.3.1.4
        }
    }
2.3.1.1 标记应用程序为已启动

接下来是遍历给定的bootstrappers来进行一系列的操作。这些bootstrappers分别为:

    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,   //加载环境变量,如.env
    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,  //加载配置文件
    \Illuminate\Foundation\Bootstrap\HandleExceptions::class,   //设置自定义错误和异常处理程序,注册shutdown处理函数。
    \Illuminate\Foundation\Bootstrap\RegisterFacades::class,    //注册Facades        
    \Illuminate\Foundation\Bootstrap\RegisterProviders::class,  //注册service providers
    \Illuminate\Foundation\Bootstrap\BootProviders::class,  //执行providers中的boot方法

LoadEnvironmentVariables::class为例分析:

2.3.1.2 调用\Illuminate\Events\Dispatcher::fire方法,也就是调用\Illuminate\Events\Dispatcher::dispatch方法
    public function dispatch($event, $payload = [], $halt = false)
    {
        // When the given "event" is actually an object we will assume it is an event
        // object and use the class as the event name and this event itself as the
        // payload to the handler, which makes object based events quite simple.
        [$event, $payload] = $this->parseEventAndPayload(   //2.3.1.2.1
            $event, $payload
        );

        if ($this->shouldBroadcast($payload)) { //2.3.1.2.2
            $this->broadcastEvent($payload[0]);
        }

        $responses = [];

        foreach ($this->getListeners($event) as $listener) {    //2.3.1.2.3
            $response = $listener($event, $payload);

            // If a response is returned from the listener and event halting is enabled
            // we will just return this response, and not call the rest of the event
            // listeners. Otherwise we will add the response on the response list.
            if ($halt && ! is_null($response)) {    //2.3.1.2.4
                return $response;
            }

            // If a boolean false is returned from a listener, we will stop propagating
            // the event to any further listeners down in the chain, else we keep on
            // looping through the listeners and firing every one in our sequence.
            if ($response === false) {  //2.3.1.2.5
                break;
            }

            $responses[] = $response;   //2.3.1.2.6
        }

        return $halt ? null : $responses;   //2.3.1.2.7
    }
2.3.1.2.1 解析事件和Payload
    protected function parseEventAndPayload($event, $payload)
    {
        if (is_object($event)) {
            [$payload, $event] = [[$event], get_class($event)];
        }

        return [$event, Arr::wrap($payload)];
    }
  • 如果第一参数为对象,那么就假定为event object,并使用其类名作为事件名,对象本身作为payload。
  • 如果第一参数为字符串,比如bootstrapping: Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables,那么第一参数就为事件名,第二参数作为payload.
2.3.1.2.2 判断payload是否有一个broadcastable event,为了集中精力,这里暂时不考虑这种情况。
2.3.1.2.3 获取指定event的所有listeners,为了集中精力,这里暂时不考虑这种情况。
2.3.1.3 解析Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables对象,并运行其bootstrap方法
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        if ($app->configurationIsCached()) {    //2.3.1.3.1
            return;
        }

        $this->checkForSpecificEnvironmentFile($app);   //2.3.1.3.2

        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load(); //2.3.1.3.3
        } catch (InvalidPathException $e) { //2.3.1.3.4
            //
        } catch (InvalidFileException $e) { //2.3.1.3.5
            echo 'The environment file is invalid: '.$e->getMessage();
            die(1);
        }
    }
2.3.1.3.1 判断配置文件是否有缓存
  • 如果存在bootstrap/cache/config.php这个文件,就有缓存。
  • 如果有缓存,就直接return,什么都不做。
2.3.1.3.2 检查是否有匹配APP_ENV的自定义environment变量文件 存在。
    /**
     * Detect if a custom environment file matching the APP_ENV exists.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    protected function checkForSpecificEnvironmentFile($app)
    {
        if ($app->runningInConsole() && ($input = new ArgvInput)->hasParameterOption('--env')) {
            if ($this->setEnvironmentFilePath(
                $app, $app->environmentFile().'.'.$input->getParameterOption('--env')
            )) {
                return;
            }
        }

        if (! env('APP_ENV')) {
            return;
        }

        $this->setEnvironmentFilePath(
            $app, $app->environmentFile().'.'.env('APP_ENV')
        );
    }
  • 判断应用程序是否运行在控制台模式。。。暂时不考虑。
  • 调用全局helpers函数env来获得环境变量APP_ENV的值。
    • 如果没有值,直接返回到2.3.1.3.3
    • 如果有值,就会将该值对应的文件设置为环境变量文件。
2.3.1.3.3 new \Dotenv\Dotenv
  • 读取指定目录下的文件(默认为.env)内容,设置环境变量,这里面的处理工作太多,了解即可。
  • 如果不发生异常,2.3.1.3.4和2.3.1.3.5都可以跳过,Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables启动完成。
2.3.1.4 与2.3.1.2相似,一个是前,一个是后,都暂时不管。

接下去依次启动其他bootstrappers。

  • 解析Illuminate\Foundation\Bootstrap\LoadConfiguration对象,启动其bootstrap()

        /**
        * Bootstrap the given application.
        *
        * @param  \Illuminate\Contracts\Foundation\Application  $app
        * @return void
        */
        public function bootstrap(Application $app)
        {
            $items = [];
    
            //首先看缓存的配置文件是否存在,如果存在,则载入缓存配置文件。
            if (file_exists($cached = $app->getCachedConfigPath())) {
                $items = require $cached;
    
                $loadedFromCache = true;
            }
    
            //注册一个config实例
            $app->instance('config', $config = new Repository($items));
    
            //如果不是从cache中获取,那么就从$app->configPath()目录下的所有配置文件中读取配置(如果有多层目录,会递归读取配置文件)。最后读出来的配置文件是一个多维数组,保存在$config变量和service container的instances数组中。
            if (! isset($loadedFromCache)) {
                $this->loadConfigurationFiles($app, $config);
            }
    
            //根据app.env的值,设置env的service container绑定
            $app->detectEnvironment(function () use ($config) {
                return $config->get('app.env', 'production');
            });
    
            //根据app.timezone的值,设置默认时区。
            date_default_timezone_set($config->get('app.timezone', 'UTC'));
    
            //Set internal character encoding to UTF-8
            mb_internal_encoding('UTF-8');
        }
  • 解析Illuminate\Foundation\Bootstrap\HandleExceptions对象,启动其bootstrap()

        /**
        * Bootstrap the given application.
        *
        * @param  \Illuminate\Contracts\Foundation\Application  $app
        * @return void
        */
        public function bootstrap(Application $app)
        {
            $this->app = $app;  //设置application instance
    
            error_reporting(-1);    //Report all PHP errors
    
            set_error_handler([$this, 'handleError']);  //Sets a user-defined error handler function
    
            set_exception_handler([$this, 'handleException']);  //Sets a user-defined exception handler function
    
            register_shutdown_function([$this, 'handleShutdown']);  //Register a function for execution on shutdown
    
            if (! $app->environment('testing')) {   //如果不是测试环境,就不显示errors信息。
                ini_set('display_errors', 'Off');
            }
        }
  • 解析Illuminate\Foundation\Bootstrap\RegisterFacades对象,运行其bootstrap()

        /**
        * Bootstrap the given application.
        *
        * @param  \Illuminate\Contracts\Foundation\Application  $app
        * @return void
        */
        public function bootstrap(Application $app)
        {
            Facade::clearResolvedInstances();   //清除所有解析过的facade实例
    
            Facade::setFacadeApplication($app); //set Facade Application 属性
    
            AliasLoader::getInstance(array_merge(   
                $app->make('config')->get('app.aliases', []),   //从config/app.php中获取所有类的别名数组
                $app->make(PackageManifest::class)->aliases()   //从bootstrap/cache/packages.php中获取所有含有aliases的项
            ))->register();  //合并上面两个数组,并将合并的数组注册到auto-loader stack
        }
  • 解析Illuminate\Foundation\Bootstrap\RegisterProviders对象,运行其bootstrap()

        public function bootstrap(Application $app)
        {
            $app->registerConfiguredProviders();    //Register all of the configured providers.
        }
        public function registerConfiguredProviders()
        {
            $providers = Collection::make($this->config['app.providers'])   //获取config/app.php中的providers
                            ->partition(function ($provider) {  //将上述providers按是否以Illuminate\\开头分为两部分
                                return Str::startsWith($provider, 'Illuminate\\');
                            });
    
            $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); //将上述providers的第二部分替换为bootstrap/cache/packages.php中的providers
    
            (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) //新建一个ProviderRepository对象
                        ->load($providers->collapse()->toArray());  //注册上述所有providers
        }
        public function load(array $providers)
        {
            $manifest = $this->loadManifest();  //载入缓存文件中的providers
    
            // First we will load the service manifest, which contains information on all
            // service providers registered with the application and which services it
            // provides. This is used to know which services are "deferred" loaders.
            if ($this->shouldRecompile($manifest, $providers)) {    //判断manifest是否需要重新compile 
                $manifest = $this->compileManifest($providers); 
            }
    
            // Next, we will register events to load the providers for each of the events
            // that it has requested. This allows the service provider to defer itself
            // while still getting automatically loaded when a certain event occurs.
            foreach ($manifest['when'] as $provider => $events) {   //注册跟事件相关的providers
                $this->registerLoadEvents($provider, $events);
            }
    
            // We will go ahead and register all of the eagerly loaded providers with the
            // application so their services can be registered with the application as
            // a provided service. Then we will set the deferred service list on it.
            foreach ($manifest['eager'] as $provider) { // register all of the eagerly loaded providers
                $this->app->register($provider);
            }
    
            $this->app->addDeferredServices($manifest['deferred']); //设置容器的deferredServices属性。
        }
  • 解析Illuminate\Foundation\Bootstrap\BootProviders,运行其bootstrap()

        public function bootstrap(Application $app)
        {
            $app->boot();   //Boot the application's service providers.
        }
        public function boot()
        {
            if ($this->booted) {    //避免重复启动
                return;
            }
    
            // Once the application has booted we will also fire some "booted" callbacks
            // for any listeners that need to do work after this initial booting gets
            // finished. This is useful when ordering the boot-up processes we run.
            $this->fireAppCallbacks($this->bootingCallbacks);   //Call the booting callbacks for the application.
    
            array_walk($this->serviceProviders, function ($p) { //调用所有非deferred的provider中的boot方法(这里面就有很多的操作了,比如添加中间件到kernel对象)。
                $this->bootProvider($p);
            });
    
            $this->booted = true;   //把程序标记为已启动
    
            $this->fireAppCallbacks($this->bootedCallbacks);    //Call the booted callbacks for the application.
        }

2.4 创建一个Pipeline对象。

    public function __construct(Container $container = null)
    {
        $this->container = $container;
    }

2.5 设置Pipeline对象的passable传输对象属性为先前传入的$request对象。

2.6 判断中间件是否已经被禁用,如果没有(默认情况),就将中间件数组赋给Pipeline对象的管道属性pipes。

小结

  • 本文主要过了一遍laravel应用程序启动的流程,可以看到需要用到的环境变量,配置参数,facades,service providers都是在这个阶段完成了注册和启动。实际上还没开始处理reqeust,任重而道远。
  • 有一些事件处理什么的,暂时没涉及到,以后再来补充。
  • 需要特别留意的主要是各个bootstraper的bootstrap()方法,还有各个service Providers的boot()方法。
  • 由于Laravel管道流原理比较复杂,待稍后再发另一篇文章进行说明,不然看起来头大。。

参考

本作品采用《CC 协议》,转载必须注明作者和本文链接
日拱一卒
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 1

我在看这部分的源代码的时候,没有想明白 2.2 清除一个已解析过的 facade 实例(目前还没有解析过的 facade 实例) 这一步的缘由所在,不知道博主有没有什么看法

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
93
粉丝
85
喜欢
153
收藏
121
排名:71
访问:11.4 万
私信
所有博文
社区赞助商