Laravel Kernel引導流程分析

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對象,在執行對象的引導方法,參數爲應用對象php

處理流程

  1. 加載並設置應用的系統環境變量(IlluminateFoundationBootstrapLoadEnvironmentVariables)laravel

    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()apache

    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)bootstrap

    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)api

    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)session

    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

    其實是經過$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']);
        }
    }
相關文章
相關標籤/搜索