Laravel $bootstrappers数组加载源码分析(二)

基于laravel10分析
在上篇文章中分析了前三个,这里继续往后分析

第四个\Illuminate\Foundation\Bootstrap\RegisterFacades::class

这个是注册静态代理
我们看一下这个类的 bootstrap 方法做了什么处理

public function bootstrap(Application $app)
 {
         //清空静态代理已解析实例的数组
        Facade::clearResolvedInstances();
        //设置容器到静态代理  如果使用静态代理拿不到实例的时候 会先从容器中去拿实例 然后去给静态代理数组
        Facade::setFacadeApplication($app);
        //别名加载器
        ①AliasLoader::getInstance(array_merge(
            //别名
            $app->make('config')->get('app.aliases', []),
            //依赖包别名 是从cache/packages.php这个缓存文件里面取aliases
            $app->make(PackageManifest::class)->aliases()
        ))->register();
}

①看一下Illuminate\Foundation\AliasLoader类的getInstance方法做了什么处理

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;
 }

②看一下Illuminate\Foundation\AliasLoader类的register方法做了什么处理

public function register()
{
        //判断是有已经注册
        if (! $this->registered) {
            //为自动加载程序堆栈准备加载方法
            $this->prependToLoaderStack();
            //标记注册
            $this->registered = true;
        }
}

protected function prependToLoaderStack()
{    
    //自动加载 当类未被定义时会触发spl_autoload_register([$this, 'load'], true, true);
}

③看一下Illuminate\Foundation\AliasLoader类的load方法做了什么处理

public function load($alias)
{
        //这里是判断空间命名是否是以Facades开头
        //如果是以Facades开头 代表是实时静态代理(可以看一下文档介绍https://learnku.com/docs/laravel/10.x/facades/14844#real-time-facades)
        if (static::$facadeNamespace && str_starts_with($alias, static::$facadeNamespace)) {
            ④$this->loadFacade($alias);

            return true;
        }

           ...
 }

④看一下Illuminate\Foundation\AliasLoader类的loadFacade方法做了什么处理

protected function loadFacade($alias)
 {
         //这里引入文件
        require $this->ensureFacadeExists($alias);
}

protected function ensureFacadeExists($alias)
{
    //判断缓存实时静态代理文件是否存在
    if (is_file($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) {
        return $path;
    }
    //如果不存在就创建文件
    file_put_contents($path, $this->formatFacadeStub(
        $alias, file_get_contents(__DIR__.'/stubs/facade.stub')
    ));

    return $path;
}

再看一下Illuminate\Foundation\AliasLoader类的load方法后续做了什么处理

public function load($alias)
{
        ...
        //如果存在別名
        if (isset($this->aliases[$alias])) {
            //为这个类创建一个别名 use 别名 等同与 use 这个类
            return class_alias($this->aliases[$alias], $alias);
        }
 }

第五个\Illuminate\Foundation\Bootstrap\RegisterProviders::class

这个是执行所有服务提供者的Register方法
我们看一下这个类的 bootstrap 方法做了什么处理

public function bootstrap(Application $app)
 {
         //这里是调用app容器的方法
        ①$app->registerConfiguredProviders();
 }

①我们看一下app容器的registerConfiguredProviders方法做了什么处理

public function registerConfiguredProviders()
{
        //这里是把官方自带的服务提供者和其他的分开成两个数组
        $providers = Collection::make($this->make('config')->get('app.providers'))
                        ->partition(fn ($provider) => str_starts_with($provider, 'Illuminate\\'));
        //这里是把安装的依赖包的服务提供者放到自带的后面,app文件夹下的服务提供者前面,应该是为了防止我们在服务提供者里面使用依赖包的类,防止报错
        $providers->splice(1, 0, [①$this->make(PackageManifest::class)->providers()]);

        ...
}

①看一下PackageManifest类的providers方法做了什么处理

public function providers()
{
    return $this->config('providers');
}

public function config($key)
{
    //这里是拿到getManifest方法返回值然后用flatMap处理成一维数组 每个value是 服务提供者
    return collect(②$this->getManifest())->flatMap(function ($configuration) use ($key) {
        return (array) ($configuration[$key] ?? []);
    })->filter()->all();
}

②看一下PackageManifest类的getManifest方法做了什么处理

protected function getManifest()
{
        //这里是防止重复调用
        if (! is_null($this->manifest)) {
            return $this->manifest;
        }
        //这个文件路径是bootstarp/cache/packages.php 如果不存在的时候就会去生成一个
        if (! is_file($this->manifestPath)) {
            //这里就是去生成
            ③$this->build();
        }
        //这里就是加载文件拿到文件中的返回值
        return $this->manifest = is_file($this->manifestPath) ?
            ④$this->files->getRequire($this->manifestPath) : [];
 }

③看一下PackageManifest类的build方法做了什么处理

public function build()
{
        $packages = [];
        //这里是判断installed.json文件是否存在
        if ($this->files->exists($path = $this->vendorPath.'/composer/installed.json')) {
            //解析json
            $installed = json_decode($this->files->get($path), true);
            //拿到packages字段 如下图
            $packages = $installed['packages'] ?? $installed;
        }
        //这里是拿到忽略的包(不需要自动发现)
        $ignoreAll = in_array('*', $ignore = $this->packagesToIgnore());
        //这里是写到bootstarp/cache/packages.php文字
        $this->write(collect($packages)->mapWithKeys(function ($package) {
            return [$this->format($package['name']) => $package['extra']['laravel'] ?? []];
        })->each(function ($configuration) use (&$ignore) {
            $ignore = array_merge($ignore, $configuration['dont-discover'] ?? []);
        })->reject(function ($configuration, $package) use ($ignore, $ignoreAll) {
            return $ignoreAll || in_array($package, $ignore);
        })->filter()->all());
}

Laravel $bootstrappers数组加载源码分析(二)

④看一下Illuminate\Filesystem\Filesystem类的getRequire方法做了什么处理

public function getRequire($path, array $data = [])
{
        if ($this->isFile($path)) {
            $__path = $path;
            $__data = $data;
            //这里用闭包的作用是防止用extract创建出来的变量会用冲突被污染
            //静态闭包的作用应该是和创建一个单独的函数一样
            return (static function () use ($__path, $__data) {
                //这里是把$__data数组中的key变成变量给$__path文件里面去用
                extract($__data, EXTR_SKIP);

                return require $__path;
            })();
        }

        throw new FileNotFoundException("File does not exist at path {$path}.");
}

再回到app容器的registerConfiguredProviders方法,看后续做了什么处理

public function registerConfiguredProviders()
    {
        ...
        //这里就是去加载拿到的服务提供者
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))->load($providers->collapse()->toArray());
    }

⑤看一下Illuminate\Foundation\ProviderRepository类的load方法做了什么处理

public function load(array $providers)
 {
        ⑥$manifest = $this->loadManifest();

       ...
 }

⑥看一下Illuminate\Foundation\ProviderRepository类的loadManifest方法做了什么处理

 public function loadManifest()
 {
        //这里就是去加载文件 bootstrap/cache/services.php
        if ($this->files->exists($this->manifestPath)) {
            //引入文件拿到返回值,getRequire方法上面有分析
            $manifest = $this->files->getRequire($this->manifestPath);

            if ($manifest) {
                return array_merge(['when' => []], $manifest);
            }
        }
 }

再回到Illuminate\Foundation\ProviderRepository类的load方法,看后续做了什么处理

public function load(array $providers)
{
    ...
    //是否应该重新生成bootstrap/cache/services.php文件
    if ($this->shouldRecompile($manifest, $providers)) {
        //这里就是去重新生成文件
        ⑦$manifest = $this->compileManifest($providers);
    }
    ...
}

public function shouldRecompile($manifest, $providers)
{    
    return 
    //表示文件不存在 或者文件无返回值  
    is_null($manifest) ||
    //这里是以上面获取到的服务提供者为准 不管获取到的是否完整
    $manifest['providers'] != $providers;
}

⑦看一下Illuminate\Foundation\ProviderRepository类的compileManifest方法做了什么处理

protected function compileManifest($providers)
{
        //创建新的服务数据结构
        $manifest = $this->freshManifest($providers);

        foreach ($providers as $provider) {
            //实例化服务提供者
            $instance = $this->createProvider($provider);
            //判断是否延迟加载
            if ($instance->isDeferred()) {
                //拿到provides定义需要延迟加载的服务
                //在这个服务提供者里面定义的服务都需要定义到provides方法中,不然就会找不到服务(http模式下,console模式下会直接去加载延迟服务)
                foreach ($instance->provides() as $service) {
                    //放入到延迟加载服务数组里面
                    $manifest['deferred'][$service] = $provider;
                }
                //这里是绑定事件,通过触发事件加载服务提供者
                $manifest['when'][$provider] = $instance->when();
            } else {
                //放到急切加载的提供者数组中
                $manifest['eager'][] = $provider;
            }
        }
        //写到bootstrap/cache/services.php文件中
        return $this->writeManifest($manifest);
}

protected function freshManifest(array $providers)
{
    return ['providers' => $providers, 'eager' => [], 'deferred' => []];
}

再回到Illuminate\Foundation\ProviderRepository类的load方法,看后续做了什么处理

public function load(array $providers)
{
    ...
    foreach ($manifest['when'] as $provider => $events) {
        //这里就是去注册事件监听绑定 当触发这个事件的时候会执行注册服务提供者
        $this->registerLoadEvents($provider, $events);
    }

    foreach ($manifest['eager'] as $provider) {
        //注册服务提供者
        ⑧$this->app->register($provider);
    }
    //把延迟服务提供者 放入容器的deferredServices数组中
    $this->app->addDeferredServices($manifest['deferred']);
}

protected function registerLoadEvents($provider, array $events)
{
    if (count($events) < 1) {
        return;
    }

    $this->app->make('events')->listen($events, fn () => $this->app->register($provider));
}

⑧看一下app容器的register方法做了什么处理

public function register($provider, $force = false)
 {
         //判断服务提供者是否已经注册 并且不强制加载
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        if (is_string($provider)) {
            //初始化服务提供者实例
            $provider = $this->resolveProvider($provider);
        }
        //去注册服务
        $provider->register();
        //这里是判断是否有bindings属性 这种就是每次去make的时候都会去重新实例化 
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }
    //这里是判断是否有singletons(单例)属性 
        if (property_exists($provider, 'singletons')) { 
            foreach ($provider->singletons as $key => $value) {
                $key = is_int($key) ? $value : $key;

                $this->singleton($key, $value);
            }
        }
        //去标记已经注册
        $this->markAsRegistered($provider);
        //这里是boot是否已经被加载
        //其中一个目的启动(boot)延迟加载服务提供者(就是去调用它的boot方法)
        if ($this->isBooted()) {
            //启动(boot)服务提供者
            $this->bootProvider($provider);
        }

        return $provider;
}

protected function bootProvider(ServiceProvider $provider)
{
    //启动前执行
    $provider->callBootingCallbacks();
    //启动
    if (method_exists($provider, 'boot')) {
        $this->call([$provider, 'boot']);
    }
    //启动后执行
    $provider->callBootedCallbacks();
}

第六个\Illuminate\Foundation\Bootstrap\BootProviders::class

这个是执行所有服务提供者的boot方法
我们看一下这个类的 bootstrap 方法做了什么处理

public function bootstrap(Application $app)
{
        //这里是调用app容器的方法
        ①$app->boot();
}

①我们看一下app容器的boot方法做了什么处理

public function boot()
{
        //如果已经启动了
        if ($this->isBooted()) {
            return;
        }

           //调用app容器的启动前执行回调
        $this->fireAppCallbacks($this->bootingCallbacks);
        //这里就是去遍历把每个元素给到闭包参数里面
        array_walk($this->serviceProviders, function ($p) {
            //这个放到上面有分析
            $this->bootProvider($p);
        });
        //标记已启动
        $this->booted = true;
         //调用app容器的启动后执行回调
        $this->fireAppCallbacks($this->bootedCallbacks);
 }

以上就是 $bootstrappers 数组后三个的执行流程了,如果有写的有误的地方,请大佬们指正

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 8个月前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!