美文网首页PHP经验分享
Laravel源码阅读 - 实例化服务容器

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

作者: AntFoot | 来源:发表于2019-06-29 14:28 被阅读109次

    通过阅读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源码阅读 - 实例化服务容器

        本文链接:https://www.haomeiwen.com/subject/krplcctx.html