资讯专栏INFORMATION COLUMN

Laravel Kernel引导流程分析

yagami / 2485人阅读

摘要:实例化各服务提供者,根据其属性将服务进行分类延迟服务即时服务,从而得到一个数组格式如,延迟处理注册延迟的服务,以后再进行调用注册延迟的事件即时处理直接进行注册调用等,并重新写入到,然后根据此文件进行相应的处理。

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 = [
    #    IlluminateFoundationBootstrapLoadEnvironmentVariables::class,
    #    IlluminateFoundationBootstrapLoadConfiguration::class,
    #    IlluminateFoundationBootstrapHandleExceptions::class,      
    #    IlluminateFoundationBootstrapRegisterFacades::class,
    #    IlluminateFoundationBootstrapRegisterProviders::class,      
    #    IlluminateFoundationBootstrapBootProviders::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 IlluminateConfigRepository($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);
    }
}
// 核心代码,获取的AppExceptionsHandle对象
protected function getExceptionHandler()
{
    // make时将会直接调用$this->bindings["IlluminateContractsDebugExceptionHandler"]["concrete"](此代码位于/var/www/laravel/bootstrap/app.php,应用对象化后,直接注入到服务容器的几个单例),返回AppExceptionsHandle对象,并将此对象注入到服务容器[参考]
    return $this->app->make(ExceptionHandler::class);
}
protected function renderHttpResponse(Exception $e)
{
    $this->getExceptionHandler()->render($this->app["request"], $e)->send();
}
// AppExceptionsHandle
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的值赋给IlluminateFoundationAliasLoader的aliases属性,并进行注册
    AliasLoader::getInstance($app->make("config")->get("app.aliases", []))->register();
}
public static function clearResolvedInstances()
{
    static::$resolvedInstance = [];
}
public static function setFacadeApplication($app)
{
    static::$app = $app;
}

IlluminateFoundationAliasLoader
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缓存文件,再对IlluminateFoundationProviderRepository进行实例化,随后加载系统配置文件(./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, " []], $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"]);
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/22606.html

相关文章

  • Laravel 请求周期

    摘要:请求周期加载自动加载器获取应用对象实例化应用解析此对象贯穿全文主要过程设置基础路径基础绑定注册全局基础服务核心容器别名设置注册三个单例获取对象实例化此对象为应用的枢纽,将会协调各部分之间的工作,完成请求主要过程注入应用对象注入事件对象注入 Laravel 请求周期 加载 composer 自动加载器 require __DIR__./../bootstrap/autoload.php;...

    Cristalven 评论0 收藏0
  • Laravel 请求生命周期

    摘要:应用实例所依赖的服务提供者可以在配置文件中的节点找到。完成所有服务提供者注册到应用实例后,应用实例执行启动方法引导项目启动。或内核接收到请求,加载服务提供者,同时,将请求分发给路由器执行。 这是一篇翻译文章,原文 Request Life Cycle of Laravel,译文 Laravel 请求生命周期 首发于个人博客,转载请注明出处。 当需要使用一个框架、工具或者服务时,在使用前...

    junfeng777 评论0 收藏0
  • Laravel中间件原理

    摘要:直到所有中间件都执行完毕,最后在执行最后的即上述的方法如果上述有地方难懂的,可以参考这边文章内置函数在中的使用以上是在通过全局中间件时的大致流程,通过中间件和路由中间件也是一样的,都是采用管道流操作,详情可翻阅源码 简介 Laravel 中间件提供了一种方便的机制来过滤进入应用的 HTTP 请求, 如ValidatePostSize用来验证POST请求体大小、ThrottleReque...

    张宪坤 评论0 收藏0
  • Laravel Kernel实例化后的处理

    摘要:所以就是对象其他的都是类似的。和一样,既可以数组形式访问,也可以按对象方式访问。大体流程是创建获取请求对象包括请求头和请求体注入请求对象到服务容器配置系统运行环境发送请求 Laravel Kernel实例化后的处理 $response = $kernel->handle( $request = IlluminateHttpRequest::capture() ); 创建并获取...

    taohonghui 评论0 收藏0
  • Laravel核心解读--服务提供器(ServiceProvider)

    摘要:调用了的可以看出,所有服务提供器都在配置文件文件的数组中。启动的启动由类负责引导应用的属性中记录的所有服务提供器,就是依次调用这些服务提供器的方法,引导完成后就代表应用正式启动了,可以开始处理请求了。 服务提供器是所有 Laravel 应用程序引导中心。你的应用程序自定义的服务、第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注册(register)和引...

    Richard_Gao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<