聊一聊Laravel的核心----服务容器

关于服务容器的基本概念,你可以看这几篇文章:
简单理解Laravel的核心概念----服务容器、服务提供者、契约

在这篇文章中,你会了解到:

  • 服务是如何被注册到服务容器的
  • 服务是究竟怎样从容器中解析的

我们知道,laravel的服务容器,会有两方面的工作:

  • 注册基础服务
  • 管理所需要的创建的类和依赖

注册基础服务

想要了解服务容器,我们需要去看看它的相关源码。在laravel中,服务容器类是Illuminate\Foundation\Application类。我们打开这个类:

namespace Illuminate\Foundation;

...

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
	...
	
    /**
     * Create a new Illuminate application instance.
     *
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

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

    /**
     * Register the basic bindings into the container.
     *
     * @return void
     */
    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()
        ));
    }
    
	...
	...
}

在App容器创建之初,我们看到构造方法中完成了三个处理功能:

  • registerBaseBindings():将App实例「服务容器」自身注册到「服务容器」
  • registerBaseServiceProviders():注册应用 Laravel 框架的基础服务提供者
  • registerCoreContainerAliases():将具体的「依赖注入容器」及其别名注册到「Laravel 服务容器」

正因为registerBaseBindings将App实例将自身注册到「服务容器」,我们才可以使用 $this->app->make(‘xxx’) 去解析一项服务。

注册基础服务提供者

    /**
     * Register all of the base service providers.
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

	...

    /**
     * Register a service provider with the application.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  bool   $force
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $force = false)
    {
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // If the given "provider" is a string, we will resolve it, passing in the
        // application instance automatically for the developer. This is simply
        // a more convenient way of specifying your service provider classes.
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        $provider->register();

        // If there are bindings / singletons set as properties on the provider we
        // will spin through them and register them with the application, which
        // serves as a convenience layer while registering a lot of bindings.
        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 the application has already booted, we will call this boot method on
        // the provider class so it has an opportunity to do its boot logic and
        // will be ready for any usage by this developer's application logic.

		执行服务提供者 boot 方法启动程序
        if ($this->isBooted()) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

	...

    /**
     * Boot the given service provider.
     * 启动给定服务提供者
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

我们可以发现在registerBaseServiceProviders方法中进行注册了三个服务提供者,分别是EventServiceProvider、LogServiceProvider、RoutingServiceProvider。

Laravel 服务容器在执行注册方法时,需要进行如下处理:

  • 将服务实现绑定到容器操作 $provider->register()
  • 如果服务提供者存在 boot 方法,会在 bootProvider 方法内执行启动方法来启动这个服务

我们可以打开LogServiceProvider看一下,在LogServiceProvider的register方法中,仅仅只是实现将闭包绑定到服务容器。

namespace Illuminate\Log;

use Illuminate\Support\ServiceProvider;

class LogServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('log', function () {
            return new LogManager($this->app);
        });
    }
}

注册服务别名到服务容器

通过Laravel Facades 和别名系统,开发者可以简单的使用服务容器中的各种服务,而注册别名和对应服务的映射关系,在registerCoreContainerAliases方法中完成,对于Facades的详情,有兴趣的可以看看我的这篇《聊一聊Laravel的核心概念----Facades》,在这里就不细说了。

管理所需创建的类及其依赖

对于服务容器来说,其bind、singleton等各种绑定方法,它们的原理是类似的。绑定方法定义在Illuminate\Foundation\Application类中,通过上面的代码,我们知道Application类继承的是Illuminate\Container\Container类,这些与服务容器绑定相关的方法便直接继承自 Container 类。

bind方法绑定原理

我们可以看一下Container类的bind方法:

    /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {

        // 如果未提供实现类 $concrete,我们直接将抽象类作为实现 $abstract。
        // 这之后,我们无需明确指定 $abstract 和 $concrete 是否为单例模式,
        // 而是通过 $shared 标识来决定它们是单例还是每次都需要实例化处理。

        $this->dropStaleInstances($abstract);

        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.

        // 如果绑定时传入的实现类非闭包,即绑定时是直接给定了实现类的类名,
        // 这时要稍微处理下将类名封装成一个闭包,保证解析时处理手法的统一
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.

        // 最后如果抽象类已经被容器解析过,我们将触发 rebound 监听器。
        // 并且通过触发 rebound 监听器回调,将任何已被解析过的服务更新最新的实现到抽象接口
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

	/**
     * Drop all of the stale instances and aliases.
     * 删除所有过时的实例和别名。
     * @param  string  $abstract
     * @return void
     */
    protected function dropStaleInstances($abstract)
    {
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }


    /**
     * 获取在构建类型时要使用的闭包
     * Get the Closure to be used when building a type.
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->resolve(
                $concrete, $parameters, $raiseEvents = false
            );
        };
    }

	...

    /**
     * Fire the "rebound" callbacks for the given abstract type.
     * 依据给定的抽象服务接口,触发其 "rebound" 回调
     * @param  string  $abstract
     * @return void
     */
    protected function rebound($abstract)
    {
        $instance = $this->make($abstract);

        foreach ($this->getReboundCallbacks($abstract) as $callback) {
            call_user_func($callback, $this, $instance);
        }
    }

    /**
     * Get the rebound callbacks for a given type.
     * 获取给定抽象服务的回调函数。
     * @param  string  $abstract
     * @return array
     */
    protected function getReboundCallbacks($abstract)
    {
        return $this->reboundCallbacks[$abstract] ?? [];
    }

在bind方法中,完成了几个方面的处理:

  • 注销解析过的服务实例;
  • 将绑定的实现类封装成闭包,以确保后续处理的统一;
  • 针对已解析过的服务实例,再次触发重新绑定回调函数,同时将最新的实现类更新到接口里面。

在绑定过程中,服务容器并不会执行服务的解析操作,这样有利于提升服务的性能。直到在项目运行期间,被使用时才会真正解析出需要使用的对应服务,实现「按需加载」。

make解析处理

make函数的声明和实现和bind方法一样都是在 Illuminate\Container\Container 类中,服务容器的解析无论是通过make等方法的手动解析还是自动注入,其实现原理都是通过PHP的反射机制

    /**
     * Resolve the given type from the container.
     * 从容器中解析出给定服务具体实现
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

	...

     * Resolve the given type from the container.
     * 从容器中解析出给定服务具体实现
     * @param  string  $abstract
     * @param  array  $parameters
     * @param  bool   $raiseEvents
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        $abstract = $this->getAlias($abstract);

		// 如果绑定时基于上下文绑定,此时需要解析出上下文实现类
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        
        // 如果给定的类型已单例模式绑定,直接从服务容器中返回这个实例而无需重新实例化
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        $concrete = $this->getConcrete($abstract);

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.

        // 已准备就绪创建这个绑定的实例。下面将实例化给定实例及内嵌的所有依赖实例。
        // 到这里我们已经做好创建实例的准备工作。只有可以构建的服务才可以执行 build 方法去实例化服务;
        // 否则也就是说我们的服务还存在依赖,然后不断的去解析嵌套的依赖,知道它们可以去构建(isBuildable)。
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.

        // 如果我们的服务存在扩展(extend)绑定,此时就需要去执行扩展。
        // 扩展绑定适用于修改服务的配置或者修饰(decorating)服务实现。
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.

        // 如果我们的服务已单例模式绑定,此时无要将已解析的服务缓存到单例对象池中(instances),
        // 后续便可以直接获取单例服务对象了。
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

	...

    /**
     * Determine if the given concrete is buildable.
     *
     * @param  mixed   $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
    	// 仅当实现类和接口相同或者实现为闭包时可构建
        return $concrete === $abstract || $concrete instanceof Closure;
    }

	...

    /**
     * Instantiate a concrete instance of the given type.
     * 构建(实例化)给定类型的实现类(匿名函数)实例
     * @param  string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete)
    {
        // If the concrete type is actually a Closure, we will just execute it and
        // hand back the results of the functions, which allows functions to be
        // used as resolvers for more fine-tuned resolution of these objects.

		// 如果给定的实现是一个闭包,直接执行并闭包,返回执行结果
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }
		
		// 使用PHP的反射
        $reflector = new ReflectionClass($concrete);

        // If the type is not instantiable, the developer is attempting to resolve
        // an abstract type such as an Interface or Abstract Class and there is
        // no binding registered for the abstractions so we need to bail out.
		
		// 如果需要解析的类无法实例化,即试图解析一个抽象类类型如: 接口或抽象类而非实现类,直接抛出异常。
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

		// 通过反射获取实现类构造函数
        $constructor = $reflector->getConstructor();

        // If there are no constructors, that means there are no dependencies then
        // we can just resolve the instances of the objects right away, without
        // resolving any other types or dependencies out of these containers.

        // 如果实现类并没有定义构造函数,说明这个实现类没有相关依赖。
        // 我们可以直接实例化这个实现类,而无需自动解析依赖(自动注入)。
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

		// 获取到实现类构造函数依赖参数
        $dependencies = $constructor->getParameters();

        // Once we have all the constructor's parameters we can create each of the
        // dependency instances and then use the reflection instances to make a
        // new instance of this class, injecting the created dependencies in.
        try {
        	// 尝试解析出所有依赖
            $instances = $this->resolveDependencies($dependencies);
        } catch (BindingResolutionException $e) {
            array_pop($this->buildStack);

            throw $e;
        }

        array_pop($this->buildStack);

		// 这是我们就可以创建服务实例并返回。
        return $reflector->newInstanceArgs($instances);
    }

	...

    /**
     * Resolve all of the dependencies from the ReflectionParameters.
     * 从 ReflectionParameters 解析出所有构造函数所需依赖
     * @param  array  $dependencies
     * @return array
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveDependencies(array $dependencies)
    {
        $results = [];

        foreach ($dependencies as $dependency) {
            // If this dependency has a override for this particular build we will use
            // that instead as the value. Otherwise, we will continue with this run
            // of resolutions and let reflection attempt to determine the result.
            //如果这个依赖项对这个特定的构建有一个覆盖,我们将使用它作为值。否则,我们将继续执行这一系列的解决方案,让反射尝试确定结果。
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);

                continue;
            }

            // If the class is null, it means the dependency is a string or some other
            // primitive type which we can not resolve since it is not a class and
            // we will just bomb out with an error since we have no-where to go.

            // 构造函数参数为非类时,即参数为 string、int 等标量类型或闭包时,按照标量和闭包解析;
            // 否则需要解析类。
            $results[] = is_null($dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
        }

        return $results;
    }

	    /**
     * Resolve a non-class hinted primitive dependency.
     * 依据类型提示解析出 标量/闭包 类型数据
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolvePrimitive(ReflectionParameter $parameter)
    {
        if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {
            return $concrete instanceof Closure ? $concrete($this) : $concrete;
        }

        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }

        $this->unresolvablePrimitive($parameter);
    }

    /**
     * Resolve a class based dependency from the container.
     * 从服务容器中解析出类依赖(自动注入)
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $this->make($parameter->getClass()->name);
        }

        // If we can not resolve the class instance, we will check to see if the value
        // is optional, and if it is we will return the optional parameter value as
        // the value of the dependency, similarly to how we do this with scalars.
        catch (BindingResolutionException $e) {
            if ($parameter->isOptional()) {
                return $parameter->getDefaultValue();
            }

            throw $e;
        }
    }

这就是make的解析原理,通过PHP的反射机制,实现了自动依赖注入和服务解析:

  1. 对于单例绑定数据,如果解析过服务则直接返回,否则继续执行解析
  2. 非单例绑定的服务类型,通过接口获取绑定实现类
  3. 接口即服务或者闭包时进行构建(build)处理,构建时依托于 PHP 反射机制进行自动依赖注入解析出完整的服务实例对象;否则继续解析(make)出所有嵌套的依赖
  4. 如果服务存在扩展绑定,解析出扩展绑定结果
  5. 如果绑定服务为单例绑定类型(singleton),将解析到的服务加入到单例对象池
  6. 其它处理如触发绑定监听器、将服务标记为已解析状态等,并返回服务实例

以上的流程就是服务容器的部分实现原理,我在浏览代码时只是比较粗的看,如果有兴趣,可以详细浏览。

参考:
http://blog.phpzendo.com/?p=353
https://laravelacademy.org/post/19434.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值