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对象,在执行对象的引导方法,参数为应用对象
处理流程
加载并设置应用的系统环境变量(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; }
将应用配置文件目录(/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!';
设置应用的错误异常等处理事件(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)); } }
根据配置项设置应用的 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魔术方法,此方法会调用相应对象里面的方法。
注入配置项的服务提供者(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,然后根据此文件进行相应的处理。
启动服务提供者的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']); } }