Laravel5.3之bootstrap源码解析

说明:Laravel在把Request经过管道Pipeline送入中间件Middleware和路由Router以前,还作了程序的启动Bootstrap工做,本文主要学习相关源码,看看Laravel启动程序作了哪些具体工做,并将我的的研究心得分享出来,但愿对别人有所帮助。Laravel在入口index.php时先加载Composer加载器:Laravel5.2之Composer自动加载,而后进行Application的实例化:Laravel5.3之IoC Container实例化源码解析,获得实例化后的Application对象再从容器中解析出Kernel服务,而后进行Request实例化(Request实例化下次再聊),而后进行Bootstrap操做启动程序,再经过Pipeline送到Middleware:Laravel5.3之Middleware源码解析,而后通过路由映射找到对该请求的操做action(之后再聊),生成Response对象通过Kernel的send()发送给Client。本文主要聊下程序的启动操做,主要作了哪些准备工做。php

开发环境:Laravel5.3 + PHP7 + OS X 10.11html

在Laravel5.3之Middleware源码解析聊过,Kernel中的sendRequestThroughRouter()处理Request,并把Request交给Pipeline送到Middleware和Router中,看源码:laravel

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

        Facade::clearResolvedInstance('request');

        /* 依次执行$bootstrappers中每个bootstrapper的bootstrap()函数,作了几件准备事情: 1. 环境检测 DetectEnvironment 2. 配置加载 LoadConfiguration 3. 日志配置 ConfigureLogging 4. 异常处理 HandleException 5. 注册Facades RegisterFacades 6. 注册Providers RegisterProviders 7. 启动Providers BootProviders protected $bootstrappers = [ 'Illuminate\Foundation\Bootstrap\DetectEnvironment', 'Illuminate\Foundation\Bootstrap\LoadConfiguration', 'Illuminate\Foundation\Bootstrap\ConfigureLogging', 'Illuminate\Foundation\Bootstrap\HandleExceptions', 'Illuminate\Foundation\Bootstrap\RegisterFacades', 'Illuminate\Foundation\Bootstrap\RegisterProviders', 'Illuminate\Foundation\Bootstrap\BootProviders', ];*/
        $this->bootstrap();

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

在Request被Pipeline送到Middleware前还有一步操做bootstrap()操做,这步操做就是启动程序,看下\Illuminate\Foundation\Http\Kernel中的bootstrap()源码:bootstrap

protected $hasBeenBootstrapped = false;
    
    ...
    
    /** * Bootstrap the application for HTTP requests. * * @return void */
    public function bootstrap()
    {
        // 检查程序是否已经启动
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
    
    public function hasBeenBootstrapped()
    {
        return $this->hasBeenBootstrapped;
    }
    
    protected function bootstrappers()
    {
        return $this->bootstrappers;
    }
    
    protected $bootstrappers = [
        'Illuminate\Foundation\Bootstrap\DetectEnvironment',
        'Illuminate\Foundation\Bootstrap\LoadConfiguration',
        'Illuminate\Foundation\Bootstrap\ConfigureLogging',
        'Illuminate\Foundation\Bootstrap\HandleExceptions',
        'Illuminate\Foundation\Bootstrap\RegisterFacades',
        'Illuminate\Foundation\Bootstrap\RegisterProviders',
        'Illuminate\Foundation\Bootstrap\BootProviders',
    ];

从以上源码可知道,程序将会依次bootstrapWith()数组$bootstrappers中各个bootstrapper,看下容器中的bootstrapWith()源码:数组

public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

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

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

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

首先触发'bootstrapping: '.$bootstrapper事件,告知将要启动该bootstrapper,而后从容器中make($bootstrapper)出该$bootstrapper,并执行该$bootstrapper中的bootstrap()方法,最后在触发事件:'bootstrapped: '.$bootstrapper,告知该$bootstrapper已经启动OK了。启动的bootstrappers就是数组$bootstrappers中的7个bootstrapper,看下程序作了哪些启动工做。浏览器

1. 环境检测

查看Illuminate\Foundation\Bootstrap\DetectEnvironment中的bootstrap()源码:缓存

public function bootstrap(Application $app)
    {
        // 查看bootstrap/cache/config.php缓存文件是否存在
        // php artisan config:cache来生成配置缓存文件,就是把config/下的全部文件放在一个缓存文件内,提升性能
        // 这里假设没有缓存配置文件
        if (! $app->configurationIsCached()) {
            $this->checkForSpecificEnvironmentFile($app);

            try {
                $env = $_ENV; // 调试添加的,此时为空
                // 这里把.env文件值取出存入$_ENV内
                (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
                // 这里$_ENV数组有值了
                $env = $_ENV;
            } catch (InvalidPathException $e) {
                //
            }
        }
    }
    
    protected function checkForSpecificEnvironmentFile($app)
    {
        // 读取$_ENV全局变量中'APP_ENV'值,此时是空
        if (! env('APP_ENV')) {
            return;
        }

        $file = $app->environmentFile().'.'.env('APP_ENV'); // .env.local

        if (file_exists($app->environmentPath().'/'.$file)) {
            $app->loadEnvironmentFrom($file);
        }
    }

环境监测核心就是把.env文件内值存入到$_ENV全局变量中\Dotenv\Dotenv::load()函数实现了这个功能,具体不详述了。能够经过Xdebug调试查看:
session

2. 配置加载

配置加载就是读取config/文件夹下的全部配置值,而后存入\Illuminate\Config\Repository对象中,而环境检测是读取.env文件存入$_ENV全局变量中,加载环境配置主要是使用\Symfony\Component\Finder\Finder这个组件进行文件查找,看下LoadConfiguration::bootstrap()的源码:app

public function bootstrap(Application $app)
    {        
        $items = [];
        // 查看config有没有缓存文件,缓存文件是在bootstrap/cache/config.php
        // 经过php artisan config:cache命令来生成缓存文件,把config/下的全部配置文件打包成一个文件,提升程序执行速度
        // 这里假设没有缓存文件
        if (file_exists($cached = $app->getCachedConfigPath())) {
            $items = require $cached;

            $loadedFromCache = true;
        }
        // 绑定服务'config',服务是\Illuminate\Config\Repository对象
        $app->instance('config', $config = new Repository($items));

        if (! isset($loadedFromCache)) {
            // 加载config/*.php全部配置文件,把全部配置存入Repository对象中
            $this->loadConfigurationFiles($app, $config);
        }
        // 检查'APP_ENV'环境设置,通常也就是'dev','stg','prd'三个环境,即'development', 'staging', 'production'
        $app->detectEnvironment(function () use ($config) {
            return $config->get('app.env', 'production');
        });

        // 设置时区,$config['app.timezone']就是调用Repository::get('app.timezone'),由于Repository实现了ArrayAccess Interface,
        // '.'语法读取是Arr::get()实现的,很好用的一个方法
        date_default_timezone_set($config['app.timezone']);

        mb_internal_encoding('UTF-8');
    }

加载配置文件,就是读取/config/*.php文件,看下源码:ide

protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
    {
        foreach ($this->getConfigurationFiles($app) as $key => $path) {
            // 存入到Repository对象中,以'key => value'存入到$items[]属性中
            $repository->set($key, require $path);
        }
    }
    
    protected function getConfigurationFiles(Application $app)
    {
        $files = [];
        // 就是'config/'这个路径
        $configPath = realpath($app->configPath());
        // Finder链式接口读取config/*.php全部文件,获取全部文件名称,而后依次遍历
        foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) {
            $nesting = $this->getConfigurationNesting($file, $configPath);

            $files[$nesting.basename($file->getRealPath(), '.php')] = $file->getRealPath();
        }

        return $files;
    }

能够经过Xdebug调试知道$files的返回值是这样的数组:

$files = [
        'app'          => '/vagrant/config/app.php', //文件的绝对路径
        'auth'         => 'vagrant/config/auth.php',
        'broadcasting' => '/vagrant/config/broadcasting.php',
        'cache'        => '/vagrant/config/cache.php',
        'compile'      => 'vagrant/config/compile.php',
        'database'     => '/vagrant/config/databse.php',
        'filesystems'  => '/vagrant/config/filesystems.php',
        'mail'         => '/vagrant/config/mail.php',
        'queue'        => '/vagrant/config/queue.php',
        'services'     => '/vagrant/config/services.php',
        'session'      => '/vagrant/config/session.php',
        'view'         => '/vagrant/config/view.php',
    ];

而后经过Application的detectEnvironment()方法把app.env的值即app.phpenv的值取出来存入Application对象的$env属性中:

public function detectEnvironment(Closure $callback)
    {
        $args = isset($_SERVER['argv']) ? $_SERVER['argv'] : null;

        return $this['env'] = (new EnvironmentDetector())->detect($callback, $args);
    }
    
    public function detect(Closure $callback, $consoleArgs = null)
    {
        if ($consoleArgs) {
            return $this->detectConsoleEnvironment($callback, $consoleArgs);
        }

        return $this->detectWebEnvironment($callback);
    }
    
    protected function detectWebEnvironment(Closure $callback)
    {
        return call_user_func($callback);
    }

因此属性检查的时候就存到了$env属性的值了,开发代码中就能够App::environment()获得这个$env属性而后进行一些操做,能够看下environment()的源码,该方法有两个feature:若是不传入值则读取$env值;若是传入值则判断该值是否与$env同样。这里若是对Application没有$env成员属性定义有疑惑,是由于PHP能够后期添加属性,如:

class ClassField
{
    
}

$class_field = new ClassField();
$class_field->name = 'Laravel';
echo $class_field->name . PHP_EOL;

/* output: Laravel

3. 日志配置

Laravel主要利用Monolog日志库来作日志处理,\Illuminate\Log\Writer至关于Monolog Bridge,把Monolog库接入到Laravel中。看下ConfigureLogging::bootstrap()源码:

public function bootstrap(Application $app)
    {
        // 注册'log'服务
        $log = $this->registerLogger($app);

        // 检查是否已经注册了Monolog
        // 这里假设开始没有注册
        if ($app->hasMonologConfigurator()) {
            call_user_func(
                $app->getMonologConfigurator(), $log->getMonolog()
            );
        } else {
            // 
            $this->configureHandlers($app, $log);
        }
    }
    
    protected function registerLogger(Application $app)
    {
        // 向容器中绑定'log'服务,即Writer对象
        $app->instance('log', $log = new Writer(
            new Monolog($app->environment()), $app['events'])
        );

        return $log;
    }

Laravel的Log模块中已经内置了几个类型的LogHandler:Single,Daily,Syslog,Errorlog.根据config/app.php文件中'log'的配置选择其中一个handler,看下configureHandlers()源码:

protected function configureHandlers(Application $app, Writer $log)
    {
        $method = 'configure'.ucfirst($app['config']['app.log']).'Handler';

        $this->{$method}($app, $log);
    }

configureHandlers()这方法也是一个技巧,找到方法名而后调用,这在Laravel中常常这么用,如Filesystem那一模块中有'create'.ucfirst(xxx).'Driver'这样的源码,是个不错的设计。这里看下configureDailyHandler()的源码,其他三个也相似:

protected function configureDailyHandler(Application $app, Writer $log)
    {
        // 解析'config'服务
        $config = $app->make('config');
        // 默认没有设置,就为null
        $maxFiles = $config->get('app.log_max_files');

        $log->useDailyFiles(
            $app->storagePath().'/logs/laravel.log', // storage/log/laravel.log
            is_null($maxFiles) ? 5 : $maxFiles, // 5
            $config->get('app.log_level', 'debug') 
        );
    }
    
    // Writer.php
    public function useDailyFiles($path, $days = 0, $level = 'debug')
    {
        $this->monolog->pushHandler(
            $handler = new RotatingFileHandler($path, $days, $this->parseLevel($level))
        );

        $handler->setFormatter($this->getDefaultFormatter());
    }

利用Mnolog的RotatingFileHandler()来往laravel.log里打印log值,固然在应用程序中常常\Log::info(),\Log::warning(),\Log::debug()来打印变量值,即Writer类中定义的的方法。Log的facade是\Illuminate\Support\Facades\Log:

class Log extends Facade
{
    /** * Get the registered name of the component. * * @return string */
    protected static function getFacadeAccessor()
    {
        return 'log';
    }
}

而'log'服务在上文中bootstrap()源码第一步registerLogger()就注册了。固然,至于使用Facade来从容器中获取服务也聊过,也不复杂,看下\Illuminate\Support\Facades\Facade的resolveFacadeInstance()源码就知道了:

protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        return static::$resolvedInstance[$name] = static::$app[$name]; // 实际上就是使用$app['log']来获取服务
    }

4. 异常处理

异常处理是十分重要的,Laravel中异常处理类\App\Exception\Handler中有一个方法report(),该方法能够用来向第三方服务(如Sentry)发送程序异常堆栈(之后在一块儿聊聊这个Sentry,效率神器),如Production Code线上环境报出个异常,能够很清楚整个堆栈,出错在哪一行:

OK,看下异常设置的启动源代码,HandleExceptions::bootstrap()的源码:

public function bootstrap(Application $app)
    {
        $this->app = $app;

        error_reporting(-1);
        // 出现错误,抛出throw new ErrorException
        set_error_handler([$this, 'handleError']);
        // 处理异常,使用report()方法来报告,可集成第三方服务Sentry来做为异常报告处理器ExceptionReportHandler
        set_exception_handler([$this, 'handleException']);

        register_shutdown_function([$this, 'handleShutdown']);

        if (! $app->environment('testing')) {
            ini_set('display_errors', 'Off');
        }
    }

这里重点看下handleException()的源码:

public function handleException($e)
    {
        if (! $e instanceof Exception) {
            $e = new FatalThrowableError($e);
        }
        // (new App\Exceptions\Handler($container))->report($e)
        $this->getExceptionHandler()->report($e);

        if ($this->app->runningInConsole()) {
            $this->renderForConsole($e);
        } else {
            $this->renderHttpResponse($e);
        }
    }
    
    protected function getExceptionHandler()
    {
        // 解析出App\Exceptions\Handler对象
        // 在boostrap/app.php中作过singleton()绑定
        return $this->app->make('Illuminate\Contracts\Debug\ExceptionHandler');
    }
    
    protected function renderHttpResponse(Exception $e)
    {
        // 使用(new App\Exceptions\Handler($container))->render(Request $request, $e)
        $this->getExceptionHandler()->render($this->app['request'], $e)->send();
    }

从源码中知道,重点是使用App\Exceptions\Handler的report()方法报告异常状况,如向Sentry报告异常堆栈和其余有用信息;App\Exceptions\Handler的render()方法经过Request发送到浏览器。关于使用第三方服务Sentry来作异常报告之后详聊,我司天天都在用这样的效率神器,很好用,值得推荐下。

5. 注册Facades

在路由文件中常常会出现Route::get()这样的写法,但实际上并无Route类,Route只是\Illuminate\Support\Facades\Route::class外观类的别名,这样取个别名只是为了简化做用,使用的是PHP内置函数class_alias(string $class, string $alias)来给类设置别名。看下RegisterFacades::bootstrap()的源码:

public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance($app->make('config')->get('app.aliases', []))->register();
    }
    
    // \Illuminate\Support\Facades\Facade
    public static function clearResolvedInstances()
    {
        static::$resolvedInstance = [];
    }
    
    // \Illuminate\Support\Facades\Facade
    public static function setFacadeApplication($app)
    {
        static::$app = $app;
    }

$app->make('config')->get('app.aliases', [])是从config/app.php中读取'aliases'的值,而后注册外观类的别名,注册的外观类有:

'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        'Auth' => Illuminate\Support\Facades\Auth::class,
        'Blade' => Illuminate\Support\Facades\Blade::class,
        'Cache' => Illuminate\Support\Facades\Cache::class,
        'Config' => Illuminate\Support\Facades\Config::class,
        'Cookie' => Illuminate\Support\Facades\Cookie::class,
        'Crypt' => Illuminate\Support\Facades\Crypt::class,
        'DB' => Illuminate\Support\Facades\DB::class,
        'Eloquent' => Illuminate\Database\Eloquent\Model::class,
        'Event' => Illuminate\Support\Facades\Event::class,
        'File' => Illuminate\Support\Facades\File::class,
        'Gate' => Illuminate\Support\Facades\Gate::class,
        'Hash' => Illuminate\Support\Facades\Hash::class,
        'Lang' => Illuminate\Support\Facades\Lang::class,
        'Log' => Illuminate\Support\Facades\Log::class,
        'Mail' => Illuminate\Support\Facades\Mail::class,
        'Notification' => Illuminate\Support\Facades\Notification::class,
        'Password' => Illuminate\Support\Facades\Password::class,
        'Queue' => Illuminate\Support\Facades\Queue::class,
        'Redirect' => Illuminate\Support\Facades\Redirect::class,
        'Redis' => Illuminate\Support\Facades\Redis::class,
        'Request' => Illuminate\Support\Facades\Request::class,
        'Response' => Illuminate\Support\Facades\Response::class,
        'Route' => Illuminate\Support\Facades\Route::class,
        'Schema' => Illuminate\Support\Facades\Schema::class,
        'Session' => Illuminate\Support\Facades\Session::class,
        'Storage' => Illuminate\Support\Facades\Storage::class,
        'URL' => Illuminate\Support\Facades\URL::class,
        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,

    ],

从以上外观别名数组中知道RouteIlluminateSupportFacadesRoute::class的别名,因此Route::get()实际上就是IlluminateSupportFacadesRoute::get(),看下AliasLoader类的getInstance()和register()方法源码:

public static function getInstance(array $aliases = [])
    {
        if (is_null(static::$instance)) {
            // 这里$aliases就是上面传进来的$aliases[],即config/app.php中'aliases'值
            return static::$instance = new static($aliases);
        }

        $aliases = array_merge(static::$instance->getAliases(), $aliases);

        static::$instance->setAliases($aliases);

        return static::$instance;
    }
    
    public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }
    
    protected function prependToLoaderStack()
    {
        // 把AliasLoader::load()放入自动加载函数堆栈中,堆栈首的位置
        spl_autoload_register([$this, 'load'], true, true);
    }

而loader()函数的源码:

public function load($alias)
    {
        if (isset($this->aliases[$alias])) {
            // @link http://php.net/manual/en/function.class-alias.php
            return class_alias($this->aliases[$alias], $alias);
        }
    }

就是经过class_alias()给外观类设置一个别名。因此Route::get()的调用过程就是,首先发现没有Route类,就去自动加载函数堆栈中经过AliasLoader::load()函数查找到RouteIlluminateSupportFacadesRoute的别名,那就调用IlluminateSupportFacadesRoute::get(),固然这里IlluminateSupportFacadesRoute没有get()静态方法,那就调用父类Facade__callStatic()来找到名为router的服务,名为'router'的服务那就是早就注册到容器中的IlluminateRoutingRouter对象,因此最终就是调用IlluminateRoutingRouter::get()方法。这个过程主要使用了两个技术:一个是外观类的别名;一个是PHP的重载,可看这篇:Laravel5.2之PHP重载(overloading)。

6. 注册Providers

外观注册是注册config/app.php中的$aliases[ ]得值,Providers注册就是注册$providers[ ]的值。看下RegisterProviders::bootstrap()的源码:

public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
    
    // Application.php
    public function registerConfiguredProviders()
    {
        // 查找bootstrap/cache/services.php有没有这个缓存文件
        // services.php这个缓存文件存储的是service providers的数组值:
        // return [
        // 'providers' => [],
        // 'eager' => [],
        // 'deferred' => [],
        // 'when' => []
        // ];
        $manifestPath = $this->getCachedServicesPath();
        
        // 经过load()方法加载config/app.php中'$providers[ ]'数组值
        (new ProviderRepository($this, new Filesystem, $manifestPath))
                    ->load($this->config['app.providers']);
    }

看下load()的源码:

public function load(array $providers)
    {
        // 查看bootstrap/cache/services.php有没有这个缓存文件
        // 第一次启动时是没有的
        $manifest = $this->loadManifest();
        // 开始没有这个缓存文件,那就把$providers[ ]里的值
        if ($this->shouldRecompile($manifest, $providers)) {
            // 而后根据$providers[ ]编译出services.php这个缓存文件
            $manifest = $this->compileManifest($providers);
        }

        foreach ($manifest['when'] as $provider => $events) {
            // 注册包含有事件监听的service provider
            // 包含有事件监听的service provider都要有when()函数返回
            $this->registerLoadEvents($provider, $events);
        }

        foreach ($manifest['eager'] as $provider) {
            // 把'eager'字段中service provider注册进容器中,
            // 即遍历每个service provider,调用其中的register()方法
            // 向容器中注册具体的服务
            $this->app->register($this->createProvider($provider));
        }

        // 注册延迟的service provider,
        // deferred的service provider, 一是要设置$defer = true,二是要提供provides()方法返回绑定到容器中服务的名称
        $this->app->addDeferredServices($manifest['deferred']);
    }

看下编译缓存文件compileManifest()方法的源码:

protected function compileManifest($providers)
    {
        $manifest = $this->freshManifest($providers);

        foreach ($providers as $provider) {
            $instance = $this->createProvider($provider);
            // 根据每个service provider的defer属性看是不是延迟加载的service provider
            if ($instance->isDeferred()) {
                // 延迟加载的,根据provides()方法提供的服务名称,写入到'deferred'字段里
                // 因此延迟加载的service provider都要提供provides()方法
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }
                // 使用when()函数提供的值注册下含有事件的service provider,
                $manifest['when'][$provider] = $instance->when();
            } else {
                // 不是延迟加载的,就放在'eager'字段里,用$this->app->register()来注册延迟加载的service provider
                $manifest['eager'][] = $provider;
            }
        }
        // 最后写入到services.php缓存文件中
        return $this->writeManifest($manifest);
    }
    
    protected function freshManifest(array $providers)
    {
        return ['providers' => $providers, 'eager' => [], 'deferred' => []];
    }

总之,注册providers就是把config/app.php中$providers[ ]定义的全部service provider中,把不是defer的service provider中绑定的服务启动起来,是defer的service provider等到须要里面绑定的服务时再执行绑定。

7. 启动Providers

最后一步,就是启动程序了,看下BootProviders::bootstrap()源码:

public function bootstrap(Application $app)
    {
        $app->boot();
    }
    
    public function boot()
    {
        // 若是程序已启动则返回,显然还没启动,还在booting状态中
        if ($this->booted) {
            return;
        }
        // 执行以前Application实例化的时候在$bootingCallbacks[]注册的回调
        $this->fireAppCallbacks($this->bootingCallbacks);
        // 以前凡是用Application::register()方法的service provider都写入到了$serviceProviders[]中
        // 这里依次执行每个service provider里的boot()方法,若是存在的话
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });

        $this->booted = true;
        // 执行以前Application实例化的时候在$bootedCallbacks[]注册的回调
        $this->fireAppCallbacks($this->bootedCallbacks);
    }
    
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

从以上源码中知道,第(7)步和第(6)步相似:第(6)是依次执行每个不是defer的service provider的register()方法;第(7)步是依次执行每个不是defer的service provider的boot()方法,若是存在的话。因此官网上service provider章节说了这么一句The Boot Method

This method is called after all other service providers have been registered, meaning you have access to all other services that have been registered by the framework

这里就明白了为啥这句话的含义了。

以前聊过Application::register()方法时里面有个检测程序是否已经启动的代码:

public function register($provider, $options = [], $force = false)
    {
        ...
        
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

刚刚开始实例化Application的时候尚未启动,在执行全部非defer的service provider boot()方法后程序就启动了:$this->booted = true;

OK, 程序启动所作的准备工做就聊完了,过程不复杂,只需一步步拆解就能基本清楚Laravel启动时作了哪些具体工做。

总结:本文主要学习了Laravel启动时作的七步准备工做:1. 环境检测 DetectEnvironment; 2. 配置加载 LoadConfiguratio; 3. 日志配置 ConfigureLogging; 4. 异常处理 HandleException;5. 注册Facades RegisterFacades;6. 注册Providers RegisterProviders;7. 启动Providers BootProviders。下次有好的技术再分享,到时见。

相关文章
相关标签/搜索