Laravel 服务容器的初始化

简介

本篇我们就 Laravel 服务容器 Application 的初始化进行 核心抽离 的方式进行讲解。

核心抽离,就是直怼最核心的代码。原因篇幅太长。。。一步一步的写,能写死我。。。

服务容器初始化起步

  • 首先在入口文件里,包含执行 bootstrap\app.php 里的代码

public/index.php

<?php

// ...上面代码省略...

// 包含执行 bootstrap/app.php 里面的代码,并取其返回值
$app = require_once __DIR__.'/../bootstrap/app.php';

// ...下面代码省略...
  • 进入 bootstrap\app.php

bootstrap\app.php

<?php

// 实例化 Laravel 服务容器,并调用其构造函数,执行初始化任务
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

// 把没有做好的 `App\Http\Kernel` 类的对象,放在 bindings 属性中, `Illuminate\Contracts\Http\Kernel` 这串字符就是标记
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

// 把没有做好的 `App\Console\Kernel` 类的对象,放在 bindings 属性中, `Illuminate\Contracts\Console\Kernel` 这串字符就是标记
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

// 把没有做好的 `App\Exceptions\Handler` 类的对象,放在 bindings 属性中, `Illuminate\Contracts\Debug\ExceptionHandler` 这串字符就是标记
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

// 返回初始化完成的 Laravel 服务容器
return $app;

注:在进行 new Illuminate\Foundation\Application 时,Application 类并没有加载到 PHP 内存中,这个时候,就用到 Composer 自动加载原理 里面讲的 loadClass 方法了,PHP 会首先执行 loadClass 方法,获取 Application 类所在绝对路径,通过 include 语句,将 Application 注入到内存。且在 include 过程中,Application 继承的父类,父类继承祖类,其中实现的接口类,会依次循环调用 loadClass 方法,直至所有用到的类全部注入 PHP 内存中,方能 new Illuminate\Foundation\Application。这里讲明,以后遇到 new 一个类不再说明。

关于 PHP realpath 方法,作用就是返回整理过的绝对路径,整理过是指去掉路径中的 /../ 这样的父文件标识。

服务容器初始化

阅读指南:服务容器 Application 和 其父类基本服务容器 Container 以及其它重要类 进行联立展示。注释以步骤记录,在两个类中来回跳转。以 (1)、(2) ... (n) 进行记录。

Application

<?php

namespace Illuminate\Foundation;

use Illuminate\Container\Container;
// use 一卡车类,因篇幅问题,省略

/**
 * 省略掉与初始化无关的代码。
 */
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    /**
     * 基本路径,记录 Laravel 应用所在的绝对路径
     */
    protected $basePath;

    /**
     * 主要存储加载过的服务提供者对象,索引数组
     */
    protected $serviceProviders = [];

    /**
     * 主要记录加载过的服务提供者,以服务提供者类名为键,值统一是 true
     */
    protected $loadedProviders = [];

    public function __construct($basePath = null)
    {
        // (1) 从 `bootstrap\app.php` 传递过来的 $basePath 是有值的
        if ($basePath) {

            // (2) 注册基本路径
            $this->setBasePath($basePath);
        }

        // (20) 注册应用基本绑定,如 app 指向容器对象
        $this->registerBaseBindings();

        // (25) 注册基本服务提供者
        $this->registerBaseServiceProviders();

        // (48) 注册标识 bindings 属性标识的获取套路
        $this->registerCoreContainerAliases();
    }

    protected function registerBaseBindings()
    {
        // (21) 设置静态属性 instance 为当前容器对象
        static::setInstance($this);

        // (22) instances 属性添加当前容器对象
        $this->instance('app', $this);

        // (23) 以 Container 为键往 instances 属性添加当前容器对象
        $this->instance(Container::class, $this);

        // (24) 初始化三方包服务提供者管理对象,供后面加载第三方用
        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }

    protected function registerBaseServiceProviders()
    {
        // (26) 注册事件服务提供者  [请到 <<ServiceProvider>> 继续阅读]
        $this->register(new EventServiceProvider($this));

        // (46) 注册日志服务提供者
        $this->register(new LogServiceProvider($this));

        // (47) 注册路由服务提供者
        $this->register(new RoutingServiceProvider($this));
    }

    public function setBasePath($basePath)
    {
        // (3) 取删除掉结尾 / 的标准绝对路径
        $this->basePath = rtrim($basePath, '\/');

        // (4) 绑定基本路径到服务容器 instances 属性中
        $this->bindPathsInContainer();

        return $this;
    }

    protected function bindPathsInContainer()
    {
        // (5) 以 path 为标识符,app 文件夹路径为值,存入 instances 属性中,如 'path' => 'D:/www/app' 
        $this->instance('path', $this->path());

        // (12) instances 属性添加应用绝对路径
        $this->instance('path.base', $this->basePath());

        // (13) instances 属性添加语言包绝对路径
        $this->instance('path.lang', $this->langPath());

        // (14) instances 属性添加配置文件夹绝对路径
        $this->instance('path.config', $this->configPath());

        // (15) instances 属性添加 public 文件夹绝对路径
        $this->instance('path.public', $this->publicPath());

        // (16) instances 属性添加 storage 文件夹绝对路径
        $this->instance('path.storage', $this->storagePath());

        // (17) instances 属性添加 database 文件夹绝对路径
        $this->instance('path.database', $this->databasePath());

        // (18) instances 属性添加 resources 文件夹绝对路径
        $this->instance('path.resources', $this->resourcePath());

        // (19) instances 属性添加 bootstrap 文件夹绝对路径
        $this->instance('path.bootstrap', $this->bootstrapPath());
    }

    public function path($path = '')
    {
        // (6) [请到 <<Container>> 继续阅读]
        return $this->basePath.DIRECTORY_SEPARATOR.'app'.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }

    public function register($provider, $options = [], $force = false)
    {
        // (28) 检测服务提供者对象有没有注册过,初始化肯定没有注册过
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // (29) 服务提供者数据类型是字符串吗,明显不是
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        // (30) 服务提供者对象有 register 方法吗,有
        if (method_exists($provider, 'register')) {

            // (31) 调用服务提供者的 register 方法 [请到 <<EventServiceProvider>> 继续阅读]
            $provider->register();
        }

        // (39) 服务提供者对象有 bindings 属性吗
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }

        // (40) 服务提供者对象有 singletons 属性吗
        if (property_exists($provider, 'singletons')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }

        // (41) 既然服务提供者已完成注册,那么记录一下,向 serviceProviders 属性和 loadedProviders 进行赋值
        $this->markAsRegistered($provider);

        // (44) 是否执行服务提供者的 boot 方法,初始化不执行
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        // (45) 返回
        return $provider;
    }

    protected function markAsRegistered($provider)
    {
        // (42) 记录服务提供者对象
        $this->serviceProviders[] = $provider;

        // (43) 记录服务提供者已经加载过
        $this->loadedProviders[get_class($provider)] = true;
    }

    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
            'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
            'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
            'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
            'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
            'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
            'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
            'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
            'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
            'db'                   => [\Illuminate\Database\DatabaseManager::class],
            'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
            'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
            'files'                => [\Illuminate\Filesystem\Filesystem::class],
            'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
            'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
            'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
            'hash'                 => [\Illuminate\Hashing\HashManager::class],
            'hash.driver'          => [\Illuminate\Contracts\Hashing\Hasher::class],
            'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
            'log'                  => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
            'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
            'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
            'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
            'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
            'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
            'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
            'redirect'             => [\Illuminate\Routing\Redirector::class],
            'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
            'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
            'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
            'session'              => [\Illuminate\Session\SessionManager::class],
            'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
            'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
            'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
            'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {

                // (49) 循环执行,给 aliases 属性和 abstractAliases 进行赋值 [请到 <<Container>> 继续阅读]
                $this->alias($key, $alias);
            }
        }
    }
}

Container

<?php

namespace Illuminate\Container;

use Closure;
// use 一卡车类,因篇幅问题,省略

/**
 * 省略掉与初始化无关的代码。
 */
class Container implements ArrayAccess, ContainerContract
{
    /**
     * 静态当前对象实例
     */
    protected static $instance;

    /**
     * 以 类名或单词 为键,生成服务实例的闭包为值,进行存储
     */
    protected $bindings = [];

    /**
     * 以 类名或单词 为键,服务实例或路径参数为值,进行存储
     */
    protected $instances = [];

    /**
     * bindings 属性 类名到单词 的映射
     */
    protected $aliases = [];

    /**
     * bindings 属性 单词到类名数组 的映射
     */
    protected $abstractAliases = [];

    public function bind($abstract, $concrete = null, $shared = false)
    {
        // (34) 删除 instances 属性和 aliases 属性上以 $abstract 为键的键值对
        $this->dropStaleInstances($abstract);

        // (35) 如果没有传递 $concrete ,则 $abstract 就是 $concrete
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // (36) 如果 $concrete 不是闭包,需要根据 $abstract 获取一个闭包类型的 $concrete
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        // (37) 核心方法,以 $abstract 为键,$concrete 和 $shared 合并成的数组为值,赋值到 bindings 属性中
        $this->bindings[$abstract] = compact('concrete', 'shared');

        // (38) 判断 $abstract 是否在 instances 中,即 $concrete 闭包被执行过了。[请到 <<Application>> 继续阅读]
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

    public function singleton($abstract, $concrete = null)
    {   
        // (33) 调用 bind 方法将 标识符 和 闭包 赋值到 bindings 属性上
        $this->bind($abstract, $concrete, true);
    }

    public function instance($abstract, $instance)
    {
        // (7) 如果 abstractAliases 属性数组中有 $abstract 这个值,则删除
        $this->removeAbstractAlias($abstract);

        // (8) 获取 $abstract 有没有绑定过的 bool 值,刚开始肯定为 false
        $isBound = $this->bound($abstract);

        // (9) 如果 aliases 属性数组有 $abstract 这个键,则删除
        unset($this->aliases[$abstract]);

        // (10) 核心方法,以 $abstract 为唯一标示, $instance 为值,进行属性赋值
        $this->instances[$abstract] = $instance;

        if ($isBound) {
            $this->rebound($abstract);
        }

        // (11) [请到 <<Application>> 继续阅读]
        return $instance;
    }

    public function alias($abstract, $alias)
    {
        // (50) 给 aliases 属性赋值
        $this->aliases[$alias] = $abstract;

        // (51) 给 abstractAliases 属性赋值
        $this->abstractAliases[$abstract][] = $alias;
    }
}

EventServiceProvider


<?php

namespace Illuminate\Events;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;

class EventServiceProvider extends ServiceProvider
{    
    public function register()
    {
        // (32) 调用容器的 singleton 方法,以 'events' 为键,闭包为值,赋值到容器的 bindings 属性中 [请到 <<Container>> 继续阅读]
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}

ServiceProvider

<?php

namespace Illuminate\Support;

use Illuminate\Console\Application as Artisan;

abstract class ServiceProvider
{
    /**
     * Application 实例
     */
    protected $app;

    public function __construct($app)
    {
        // (27) 赋值 app 属性为 Application 实例 [请到 <<Application>> 继续阅读]
        $this->app = $app;
    }

    // ...省掉暂时无用代码
}

本篇如有错误、不当或者需补充的内容,请各位同僚多提宝贵意见。

本文章首发在 LearnKu.com 网站上。
上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~