1

Laravel Kernel引导流程分析

代码展示

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

    # Facade::clearResolvedInstance('request');
    // 主要是这句代码
    $this->bootstrap();

    # return (new Pipeline($this->app))
    #            ->send($request)
    #            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
    #            ->then($this->dispatchToRouter());
}
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}
protected function bootstrappers()
{
    #####################################################################
    #$bootstrappers = [
    #    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    #    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    #    \Illuminate\Foundation\Bootstrap\HandleExceptions::class,      
    #    \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    #    \Illuminate\Foundation\Bootstrap\RegisterProviders::class,      
    #    \Illuminate\Foundation\Bootstrap\BootProviders::class,
    #];
    #####################################################################
    return $this->bootstrappers;
}
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]);
    }
}

$this->make($bootstrapper)->bootstrap($this):会先创建$bootstrapper对象,在执行对象的引导方法,参数为应用对象

处理流程

  1. 加载并设置应用的系统环境变量(IlluminateFoundationBootstrapLoadEnvironmentVariables)

    public function bootstrap(Application $app)
    {
        // /var/www/laravel/bootstrap/cache/config.php 存在则直接返回
        if ($app->configurationIsCached()) {
            return;
        }
    
        $this->checkForSpecificEnvironmentFile($app);
    
        try {
            // 委托Dotenv来临时设置此次请求的系统环境变量,默认传参依次为'/var/www/laravel'和'.env'
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
        } catch (InvalidPathException $e) {
            //
        }
    }
    protected function checkForSpecificEnvironmentFile($app)
    {
        // cli模式下,并且存在--env参数(类似命令为: cammond --env=example)
        if (php_sapi_name() == 'cli' && with($input = new ArgvInput)->hasParameterOption('--env')) {
            // 将系统环境文件(类似:/var/www/laravel/.env.example)设置为$app应用的environmentFile属性,供后面使用
            $this->setEnvironmentFilePath(
                $app, $app->environmentFile().'.'.$input->getParameterOption('--env')
            );
        }
    
        if (! env('APP_ENV')) {
            return;
        }
    
        $this->setEnvironmentFilePath(
            $app, $app->environmentFile().'.'.env('APP_ENV')
        );
    }
    

    (new Dotenv($app->environmentPath(), $app->environmentFile()))->load()

    public function __construct($path, $file = '.env')
    {
        // 类似/var/www/laravel/.env
        $this->filePath = $this->getFilePath($path, $file);
        // 创建加载器,委托Loader处理
        $this->loader = new Loader($this->filePath, true);
    }
    protected function getFilePath($path, $file)
    {
        if (!is_string($file)) {
            $file = '.env';
        }
    
        $filePath = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$file;
    
        return $filePath;
    }
    public function load()
    {
        return $this->loadData();
    }
    protected function loadData($overload = false)
    {
        $this->loader = new Loader($this->filePath, !$overload);
    
        return $this->loader->load();
    }
    

    new Loader($this->filePath, !$overload)

    public function __construct($filePath, $immutable = false)
    {
        $this->filePath = $filePath;
        $this->immutable = $immutable;
    }
    public function load()
    {
        $this->ensureFileIsReadable();
    
        $filePath = $this->filePath;
        $lines = $this->readLinesFromFile($filePath);
        foreach ($lines as $line) {
            // 如果行不是注释行且含有=号,则进行
            if (!$this->isComment($line) && $this->looksLikeSetter($line)) {
                $this->setEnvironmentVariable($line);
            }
        }
    
        return $lines;
    }
    // 将文件按行的形式读入到数组并返回
    protected function readLinesFromFile($filePath)
    {
        $autodetect = ini_get('auto_detect_line_endings');
        ini_set('auto_detect_line_endings', '1');
        $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        ini_set('auto_detect_line_endings', $autodetect);
    
        return $lines;
    }
    public function setEnvironmentVariable($name, $value = null)
    {
        // 检测过滤校验环境变量
        list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);
        // 当immutable为真时,不覆盖对应的环境变量
        if ($this->immutable && $this->getEnvironmentVariable($name) !== null) {
            return;
        }
        // apache运行环境下,尝试临时覆盖系统环境变量
        if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
            apache_setenv($name, $value);
        }
        // 尝试临时设置当前请求的系统环境变量
        if (function_exists('putenv')) {
            putenv("$name=$value");
        }
        // 赋值全局变量
        $_ENV[$name] = $value;
        $_SERVER[$name] = $value;
    }
    
  2. 将应用配置文件目录(/var/www/laravel/config)下所有php文件返回的数组载入到$config对象(IlluminateFoundationBootstrapLoadConfiguration)

    public function bootstrap(Application $app)
    {
        $items = [];
        // /var/www/laravel/bootstrap/cache/config.php文件[配置文件的缓存合集,加快加载速度]存在则载入,并标记已加载
        if (file_exists($cached = $app->getCachedConfigPath())) {
            $items = require $cached;
    
            $loadedFromCache = true;
        }
        // 构建config对象,并注入到服务容器
        $app->instance('config', $config = new Repository($items));
    
        if (! isset($loadedFromCache)) {
            // 将系统的配置文件载入到$config对象
            $this->loadConfigurationFiles($app, $config);
        }
        // 设置$this['env']为系统环境变量app.env,没有则默认为production
        $app->detectEnvironment(function () use ($config) {
            return $config->get('app.env', 'production');
        });
    
        date_default_timezone_set($config->get('app.timezone', 'UTC'));
    
        mb_internal_encoding('UTF-8');
    }
    
    $config = new \Illuminate\Config\Repository($items)
    public function __construct(array $items = [])
    {
        $this->items = $items;
    }
    
    protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
    {
        foreach ($this->getConfigurationFiles($app) as $key => $path) {
            // 此操作将在$repository对象里面构造一个多维数组属性$this->items,值为相应的系统配置文件返回的数组,后续可以直接通过get获取
            $repository->set($key, require $path);
        }
    }
    /** $files数组形式如下
        [
            'app' => '/var/www/laravel/config/app.php',
            'auth' => '/var/www/laravel/config/auth.php',
            'xx.file' => '/var/www/laravel/config/xx/file.php',
            'xx.yy.file' => '/var/www/laravel/config/xx/yy/file.php',
        ]
    */
    protected function getConfigurationFiles(Application $app)
    {
        $files = [];
        // 系统配置文件的路径(/var/www/laravel/config)
        $configPath = realpath($app->configPath());
        // 文件相关的操作委托给Finder类(很强大)来处理,Finder实现了IteratorAggregate的getIterator方法
        foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) {
            // 迭代/var/www/laravel/config下面嵌套的层层子目录构造成.形式的目录
            $directory = $this->getNestedDirectory($file, $configPath);
            
            $files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();
        }
    
        return $files;
    }
    
    $repository->set($key, require $path)
    // 构造将.形式转变为相应层级的数组$this->items。比如:$key='xx.yy.file',$value='/var/www/laravel/config/xx/yy/file.php',将会构建为:$this->items['xx']['yy']['file'] = $value返回的数组。
    public function set($key, $value = null)
    {
        $keys = is_array($key) ? $key : [$key => $value];
    
        foreach ($keys as $key => $value) {
            Arr::set($this->items, $key, $value);
        }
    }
    

    根据默认的系统配置文件目录,以上操作的结果如下:
    $config对象(new Repository)里面的$this->items数组属性,后期可以通过$config->get()来获取

    $this->items['app'] = /var/www/laravel/config/app.php返回的数组;
    $this->items['auth'] = /var/www/laravel/config/auth.php返回的数组;
    $this->items['broadcasting'] = /var/www/laravel/config/broadcasting.php返回的数组;
    $this->items['cache'] = /var/www/laravel/config/cache.php返回的数组;
    $this->items['database'] = /var/www/laravel/config/database.php返回的数组;
    $this->items['filesystems'] = /var/www/laravel/config/filesystems.php返回的数组;
    $this->items['mail'] = /var/www/laravel/config/mail.php返回的数组;
    $this->items['queue'] = /var/www/laravel/config/queue.php返回的数组;
    $this->items['services'] = /var/www/laravel/config/services.php返回的数组;
    $this->items['session'] = /var/www/laravel/config/session.php返回的数组;
    $this->items['view'] = /var/www/laravel/config/view.php返回的数组;
    
    假如有这样的文件(/var/www/laravel/config/xx/yy/zz/file.php),返回['a'=>'hello,world!']数组
    将得到:$this->items['xx']['yy']['zz']['file'] = ['a'=>'hello,world!'];
    获取方式: $config->get('xx.yy.zz.file.a', $default),直接返回'hello,world!';
    
  3. 设置应用的错误异常等处理事件(IlluminateFoundationBootstrapHandleExceptions)

    public function bootstrap(Application $app)
    {
        $this->app = $app;
    
        error_reporting(-1);
    
        set_error_handler([$this, 'handleError']);
    
        set_exception_handler([$this, 'handleException']);
    
        register_shutdown_function([$this, 'handleShutdown']);
    
        if (! $app->environment('testing')) {
            ini_set('display_errors', 'Off');
        }
    }
    public function handleError($level, $message, $file = '', $line = 0, $context = [])
    {
        if (error_reporting() & $level) {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
    }
    public function handleException($e)
    {
        if (! $e instanceof Exception) {
            $e = new FatalThrowableError($e);
        }
    
        $this->getExceptionHandler()->report($e);
    
        if ($this->app->runningInConsole()) {
            $this->renderForConsole($e);
        } else {
            $this->renderHttpResponse($e);
        }
    }
    // 核心代码,获取的\App\Exceptions\Handle对象
    protected function getExceptionHandler()
    {
        // make时将会直接调用$this->bindings['Illuminate\Contracts\Debug\ExceptionHandler']['concrete'](此代码位于/var/www/laravel/bootstrap/app.php,应用对象化后,直接注入到服务容器的几个单例),返回\App\Exceptions\Handle对象,并将此对象注入到服务容器[参考]
        return $this->app->make(ExceptionHandler::class);
    }
    protected function renderHttpResponse(Exception $e)
    {
        $this->getExceptionHandler()->render($this->app['request'], $e)->send();
    }
    // \App\Exceptions\Handle
    public function render($request, Exception $e)
    {
        $e = $this->prepareException($e);
    
        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {
            return $this->convertValidationExceptionToResponse($e, $request);
        }
    
        return $this->prepareResponse($request, $e);
    }
    protected function renderHttpException(HttpException $e)
    {
        $status = $e->getStatusCode();
    
        view()->replaceNamespace('errors', [
            resource_path('views/errors'),
            __DIR__.'/views',
        ]);
    
        if (view()->exists("errors::{$status}")) {
            return response()->view("errors::{$status}", ['exception' => $e], $status, $e->getHeaders());
        } else {
            return $this->convertExceptionToResponse($e);
        }
    }
    public function handleShutdown()
    {
        if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
            $this->handleException($this->fatalExceptionFromError($error, 0));
        }
    }
    
  4. 根据配置项设置应用的 Facades(IlluminateFoundationBootstrapRegisterFacades)

    public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();
    
        Facade::setFacadeApplication($app);
        // 将配置文件/var/www/laravel/config/app.php返回数组的键为aliases的值赋给\Illuminate\Foundation\AliasLoader的aliases属性,并进行注册
        AliasLoader::getInstance($app->make('config')->get('app.aliases', []))->register();
    }
    public static function clearResolvedInstances()
    {
        static::$resolvedInstance = [];
    }
    public static function setFacadeApplication($app)
    {
        static::$app = $app;
    }
    
    \Illuminate\Foundation\AliasLoader
    public static function getInstance(array $aliases = [])
    {
        if (is_null(static::$instance)) {
            return static::$instance = new static($aliases);
        }
    
        $aliases = array_merge(static::$instance->getAliases(), $aliases);
    
        static::$instance->setAliases($aliases);
    
        return static::$instance;
    }
    private function __construct($aliases)
    {
        $this->aliases = $aliases;
    }
    public function getAliases()
    {
        return $this->aliases;
    }
    public function setAliases(array $aliases)
    {
        $this->aliases = $aliases;
    }
    public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();
    
            $this->registered = true;
        }
    }
    protected function prependToLoaderStack()
    {
        // 将$this->load注册到自动加载器的最前面,失败时抛异常
        spl_autoload_register([$this, 'load'], true, true);
    }
    public function load($alias)
    {
        // $facadeNamespace = 'Facades\\',估计是框架内部使用的,以后再看吧
        if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
            $this->loadFacade($alias);
    
            return true;
        }
    
        if (isset($this->aliases[$alias])) {
            return class_alias($this->aliases[$alias], $alias);
        }
    }
    

    Facade的本质

    实际上是通过$app->make('config')->get('app.aliases', [])取出config/app.php文件里面的aliases数组并实例化AliasLoader,再将AliasLoader->load方法放到spl自动加载器最前面,最后通过class_alias($this->aliases[$alias], $alias)。当调用Cache::Method时,会触发Facdes的__callStatic魔术方法,此方法会调用相应对象里面的方法。

  5. 注入配置项的服务提供者(IlluminateFoundationBootstrapRegisterProviders)

    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
    public function registerConfiguredProviders()
    {
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($this->config['app.providers']);
    }
    public function getCachedServicesPath()
    {
        return $this->bootstrapPath().'/cache/services.php';
    }
    
    // 先取services缓存文件,再对\Illuminate\Foundation\ProviderRepository进行实例化,随后加载系统配置文件(./config/app.php)里面的providers数组
    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($this->config['app.providers'])
    public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
    {
        $this->app = $app;
        $this->files = $files;
        $this->manifestPath = $manifestPath;
    }
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();
    
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }
    
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }
    
        foreach ($manifest['eager'] as $provider) {
            // 直接注册服务(将直接调用服务的register方法)
            $this->app->register($provider);
        }
    
        $this->app->addDeferredServices($manifest['deferred']);
    }
    public function loadManifest()
    {
        if ($this->files->exists($this->manifestPath)) {
            $manifest = $this->files->getRequire($this->manifestPath);
    
            if ($manifest) {
                return array_merge(['when' => []], $manifest);
            }
        }
    }
    public function shouldRecompile($manifest, $providers)
    {
        return is_null($manifest) || $manifest['providers'] != $providers;
    }
    protected function compileManifest($providers)
    {
        $manifest = $this->freshManifest($providers);
    
        foreach ($providers as $provider) {
            $instance = $this->createProvider($provider);
            // 延迟加载的服务
            if ($instance->isDeferred()) {
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }
                // 注册延迟的事件
                $manifest['when'][$provider] = $instance->when();
            }
            // 即时加载的服务
            else {
                $manifest['eager'][] = $provider;
            }
        }
    
        return $this->writeManifest($manifest);
    }
    protected function freshManifest(array $providers)
    {
        return ['providers' => $providers, 'eager' => [], 'deferred' => []];
    }
    public function createProvider($provider)
    {
        return new $provider($this->app);
    }
    public function isDeferred()
    {
        return $this->defer;
    }
    public function writeManifest($manifest)
    {
        if (! is_writable(dirname($this->manifestPath))) {
            throw new Exception('The bootstrap/cache directory must be present and writable.');
        }
    
        $this->files->put(
            $this->manifestPath, '<?php return '.var_export($manifest, true).';'
        );
    
        return array_merge(['when' => []], $manifest);
    }
    protected function registerLoadEvents($provider, array $events)
    {
        if (count($events) < 1) {
            return;
        }
    
        $this->app->make('events')->listen($events, function () use ($provider) {
            $this->app->register($provider);
        });
    }
    public function addDeferredServices(array $services)
    {
        $this->deferredServices = array_merge($this->deferredServices, $services);
    }
    

    大致流程

    通过/var/www/laravel/bootstrap/cache/services.php等实例化IlluminateFoundationProviderRepository,并加载$this->config['app.providers']数组。实例化app.providers各服务提供者,根据其defer属性将服务进行分类(延迟服务|即时服务),从而得到一个$manifest数组(格式如services.php,延迟处理:deferred=>注册延迟的服务,以后再进行调用;when=>注册延迟的事件;即时处理:eager=>直接进行注册调用等),并重新写入到services.php,然后根据此文件进行相应的处理。

  6. 启动服务提供者的boot方法等操作(IlluminateFoundationBootstrapBootProviders)

    public function bootstrap(Application $app)
    {
        $app->boot();
    }
    public function boot()
    {
        if ($this->booted) {
            return;
        }
        // 可以通过应用的booting方法来注册服务启动前的事件监听者
        $this->fireAppCallbacks($this->bootingCallbacks);
        // 尝试调用所有的服务提供者的boot方法
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });
    
        $this->booted = true;
        // 可以通过应用的booted方法来注册服务启动后的事件监听者,若已经启用了,则直接出发事件
        $this->fireAppCallbacks($this->bootedCallbacks);
    }
    protected function fireAppCallbacks(array $callbacks)
    {
        foreach ($callbacks as $callback) {
            call_user_func($callback, $this);
        }
    }
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

TylerZou
70 声望20 粉丝

I have a dream!