Laravel源码阅读 - 实例化服务容器

96
AntFoot
0.1 2019.06.29 14:28* 字数 1501

通过阅读Laravel源码,配合官方文档介绍,了解Laravel框架的运行流程。本章介绍启动应用程序的时候,初始化服务容器过程中,执行的一系列基础内容和服务的注册,绑定。最后生成一个服务容器

启动应用程序介绍

Laravel应用的所有请求入口是public/index.php文件,首先看该文件中的代码以及简单介绍
文件:bootstrap/app.php

<?php
    define('LARAVEL_START', microtime(true));
    // 载入Composer生成的自动加载文件
    require __DIR__.'/../vendor/autoload.php';
    // 启动应用,完成服务容器的实例化和基本注册
    $app = require_once __DIR__.'/../bootstrap/app.php';
    // 处理请求并返回响应结果
    $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
    $response = $kernel->handle(
        $request = Illuminate\Http\Request::capture()
    );
    $response->send();
    $kernel->terminate($request, $response);

入口文件中的代码,简单清晰的展示了请求到响应的整个生命周期的过程,首先是加载Composer的自动加载文件,然后实例化服务容器,并注册核心服务。然后通过kernel内核处理请求并返回响应结果。

服务容器实例化过程

启动Laravel应用程序的入口文件中,通过$app = require_once __DIR__.'/../bootstrap/app.php';获取服务容器对象,查看实例化服务容器过程中的详细代码片段,然后再逐步了解每一步的意图
文件: bootstrap/app.php

<?php
// 服务容器的实例化
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
// 绑定 接口和对应到
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);
return $app;

这段代码中,首先是实例化Illuminate\Foundation\Application类,得到一个对象,即服务容器对象。继续查看实例化这个类的过程中都做了哪些处理。
文件:vendor/laravel/framework/src/Illuminate/Foundation/Application.php

    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

这个构造函数中,依次实现设置应用的基本路径,注册基础绑定,注册基础服务提供者,最后注册核心类的别名

1. 设置基本路径
if ($basePath) {
    $this->setBasePath($basePath);
}

为应用设置基本路径

    public function setBasePath($basePath)
    {
        $this->basePath = rtrim($basePath, '\/');

        $this->bindPathsInContainer();

        return $this;
    }

为服务容器绑定所有项目应用的路径

    protected function bindPathsInContainer()
    {
        $this->instance('path', $this->path());
        $this->instance('path.base', $this->basePath());
        $this->instance('path.lang', $this->langPath());
        $this->instance('path.config', $this->configPath());
        $this->instance('path.public', $this->publicPath());
        $this->instance('path.storage', $this->storagePath());
        $this->instance('path.database', $this->databasePath());
        $this->instance('path.resources', $this->resourcePath());
        $this->instance('path.bootstrap', $this->bootstrapPath());
    }
2. 注册基础绑定到容器

$this->registerBaseBindings();这段代码用来向容器中注册绑定基础服务。
文件:vendor/laravel/framework/src/Illuminate/Foundation/Application.php

    protected function registerBaseBindings()
    {
        static::setInstance($this);

        $this->instance('app', $this);

        $this->instance(Container::class, $this);
        $this->singleton(Mix::class);

        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }

static::setInstance($this);设置当前容器对象为当前的全局容器,后续代码中,可以通过
文件:vendor/laravel/framework/src/Illuminate/Container/Container.php中的静态方法getInstance()直接获取服务容器实例。

    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }

        return static::$instance;
    }

$this->instance('app', $this);,$this->instance(Container::class, $this);
向服务容器的共享实例数组(instances)中注册两个单例服务。在后续的使用中,可以通过此处绑定的别名找到对应的服务容器实例。
同理,代码$this->instance(PackageManifest::class, new PackageManifest( new Filesystem, $this->basePath(), $this->getCachedPackagesPath() ));也向instances数组中,绑定一个文件系统单例服务。
instance方法的实现内容:
文件:vendor/laravel/framework/src/Illuminate/Container/Container.php

    public function instance($abstract, $instance)
    {
        $this->removeAbstractAlias($abstract);
        $isBound = $this->bound($abstract);
        unset($this->aliases[$abstract]);
        $this->instances[$abstract] = $instance;
        if ($isBound) {
            $this->rebound($abstract);
        }
        return $instance;
    }

$this->removeAbstractAlias($abstract);从上下文绑定别名缓存中删除该别名

    protected function removeAbstractAlias($searched)
    {
        // 这个$this->aliases数组存放的是已注册的类名的别名
        if (! isset($this->aliases[$searched])) {
            return;
        }

        foreach ($this->abstractAliases as $abstract => $aliases) {
            foreach ($aliases as $index => $alias) {
                if ($alias == $searched) {
                    unset($this->abstractAliases[$abstract][$index]);
                }
            }
        }
    }

$isBound = $this->bound($abstract);判断是否已绑定给定的抽象类型。
$this->instances[$abstract] = $instance; 将传进来的instance对象和abstract接口注册到容器中到共享实例数组($instances)中

3. 注册基础服务提供者

$this->registerBaseServiceProviders();这行代码实现向服务容器注册最基础的服务提供者。分别为事件服务,日志服务和路由服务
文件:vendor/laravel/framework/src/Illuminate/Foundation/Application.php

    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

向服务容器提供这些基础服务,首先实例化服务提供者,然后通过容器中的register方法完成注册。注册过程:
文件:vendor/laravel/framework/src/Illuminate/Foundation/Application.php

    public function register($provider, $force = false)
    {
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }
        $provider->register();
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }
        if (property_exists($provider, 'singletons')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }
        $this->markAsRegistered($provider);
        if ($this->booted) {
            $this->bootProvider($provider);
        }
        return $provider;
    }

首先,判断该服务提供者是否已经被注册过,并且$force = false,则直接返回注册结果
然后,判断传进来的provider是否为字符串,如果是,应该是一个类名的字符串,则实例化该服务提供者。
拿到未被注册过的服务提供者的对象之后名,调用该对象的register方法

每个服务提供者都会继承Illuminate\Support\ServiceProvider。在该类中有一个register方法,然后每个服务提供者都会各自实现这个register方法,用来提供自己的服务。

$this->markAsRegistered($provider);标示该服务器提供者已经完成注册。在该方法中会将完成注册的服务提供者的对象存入serviceProviders数组中,然后将该服务器提供者的类名作为key,value设置为true的键值对的形式,存储loadedProviders数组中。

4. 注册核心类别名
    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [self::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) {
                $this->alias($key, $alias);
            }
        }
    }

这段代码主要是将这个框架用到的核心服务设置一个别名,在服务解析过过程中,需要根据类名或者接口名找到服务别名,然后通过服务别名获取具体的服务。

向服务容器绑定一些重要接口

文件:bootstrap/app.php

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

这里$app就是服务容器对象,调用其singleton()方法是向服务容器中注册一个共享绑定。这个方法接收的第一个参数作为服务名的接口,第二个参数作为提供服务的类,在方法内部会通过反射机制,将其实例化之后。存入容器。
文件:vendor/laravel/framework/src/Illuminate/Container/Container.php

    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

这个方法内部只是调用了bind()方法,并且第三个参数传入true,表示作为共享服务绑定到容器。

    public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);
        if (is_null($concrete)) {
            $concrete = $abstract;
        }
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
        $this->bindings[$abstract] = compact('concrete', 'shared');
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

这个方法中,是将传进来的接口参数和提供服务的回调函数绑定到容器的bindings数组中。

至此,应用程序的准备工作就已经做完了,生成了一个服务容器,并向容器中注册绑定了一些基础服务,以及绑定了一些重要的接口。下一篇文章<<Laravel源码阅读 - 接收请求并返回结果>>介绍应用接收到http请求并返回结果的内容

laravel源码阅读