laravel核心架构——服务容器

简介

Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具。依赖注入这个花俏名词实质上是指:类的依赖项通过构造函数,或者某些情况下通过「setter」方法「注入」到类中。

Laravel服务容器的功能多由Illuminate\Container\Container类提供,由于Illuminate\Foundation\Application类继承了该类,在使用服务容器时可以通过Illuminate\Foundation\Application类实例进行方法调用。
laravel核心架构——服务容器_第1张图片

绑定

基础绑定

简单绑定

在服务提供器中,你可以通过 $this->app 属性访问容器。我们可以通过 bind 方法注册绑定,传递我们想要注册的类或接口名称再返回类的实例的 Closure

$this->app->bind('HelpSpot\API', function ($app) {
     
    return new HelpSpot\API($app->make('HttpClient'));
});

下面是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)
    {
     
        // 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.
        $this->dropStaleInstances($abstract);

        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.
        if ($this->resolved($abstract)) {
     
            $this->rebound($abstract);
        }
    }

bind方法的第一个参数可以是要注册的类名或接口名称,第二个参数可以是一个闭包函数,如果不是一个闭包函数,将会调用getClosure方法包装成一个闭包函数。
bind方法主要的内容是把要注册的类名或接口名称作为键名,闭包函数作为键值添加到bindings属性数组中。
执行绑定即调用bind方法时,并不会创建绑定的对应类的对象。

绑定一个单例

singleton 方法将类或接口绑定到只能解析一次的容器中。绑定的单例被解析后,相同的对象实例会在随后的调用中返回到容器中:

$this->app->singleton('HelpSpot\API', function ($app) {
     
    return new HelpSpot\API($app->make('HttpClient'));
});

singleton方法的源代码:

    /**
     * Register a shared binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null)
    {
     
        $this->bind($abstract, $concrete, true);
    }

singleton方法内调用了bind方法,只不过第三个参数$shared设置为true,表示这个类是共享型。
bind方法的$shared参数设置为true时,创建绑定的对象时,会先检查instances属性数组中是否已创建该绑定的对象,如果有就不再创建对象,如果没有创建相应对象,且把创建的对象添加到instances属性数组中。

绑定实例

你也可以使用 instance 方法将现有对象实例绑定到容器中。给定的实例会始终在随后的调用中返回到容器中:

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\API', $api);

下面是instance方法的源代码:

    /**
     * Register an existing instance as shared in the container.
     *
     * @param  string  $abstract
     * @param  mixed   $instance
     * @return mixed
     */
    public function instance($abstract, $instance)
    {
     
        $this->removeAbstractAlias($abstract);

        $isBound = $this->bound($abstract);

        unset($this->aliases[$abstract]);

        // We'll check to determine if this type has been bound before, and if it has
        // we will fire the rebound callbacks registered with the container and it
        // can be updated with consuming classes that have gotten resolved here.
        $this->instances[$abstract] = $instance;

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

        return $instance;
    }

instance方法主要的内容是把对象添加到instances属性数组中。instance属性数组保存着服务容器已绑定的对象。

解析

make方法

你可以使用 make 方法将容器中的类实例解析出来。make 方法接受要解析的类或接口的名称:

$api = $this->app->make('HelpSpot\API');

make方法中调用了resolve方法:

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = [])
    {
     
        return $this->resolve($abstract, $parameters);
    }

如果你的代码处于不能访问 $app 变量的位置,你可以使用全局的辅助函数 resolve

$api = resolve('HelpSpot\API');

下面是辅助方法resolve的定义:

if (! function_exists('resolve')) {
     
    /**
     * Resolve a service from the container.
     *
     * @param  string  $name
     * @return mixed
     */
    function resolve($name)
    {
     
        return app($name);
    }
}

if (! function_exists('app')) {
     
    /**
     * Get the available container instance.
     *
     * @param  string  $abstract
     * @param  array   $parameters
     * @return mixed|\Illuminate\Foundation\Application
     */
    function app($abstract = null, array $parameters = [])
    {
     
        if (is_null($abstract)) {
     
            return Container::getInstance();
        }

        return Container::getInstance()->make($abstract, $parameters);
    }
}
  • resovle方法内使用辅助方法app获取对象。
  • app方法内使用Applicationmake方法进行解析。
  • Container类中的instance属性保存着laravel应用实例,即Application类的对象。通过静态方法getInstance可以获取改应用实例。
  • 最终由Container类的resolve方法进行绑定类名的解析。

下面是resolve方法的源代码:

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    protected function resolve($abstract, $parameters = [])
    {
     
        $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.
        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.
        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.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
     
            $this->instances[$abstract] = $object;
        }

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

resolve内查找aliasesabstractAliasescontextualbindings多个属性数组,找到最终要创建的类名,调用build方法创建对象。

下面是build方法的源代码:

    /**
     * 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());
        }

        $reflector = new ReflectionClass($concrete);

        // If the type is not instantiable, the developer is attempting to resolve
        // an abstract type such as an Interface of 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.
        $instances = $this->resolveDependencies(
            $dependencies
        );

        array_pop($this->buildStack);

        return $reflector->newInstanceArgs($instances);
    }

自动注入

你可以简单地使用「类型提示」的方式在由容器解析的类的构造函数中添加依赖项,包括 控制器、事件监听器、队列任务、中间件 等。 事实上,这是你的大多数对象也应该由容器解析。

容器中绑定类的对象创建是build方法执行的,build方法中通过php的反射类ReflectionClass方式进行对象的创建。
build方法中调用resolveDependencies方法进行依赖处理。如果类构造方法参数中依赖于其他的类对象,将会通过make方法从服务容器中获取该类的对象。

容器事件

每当服务容器解析一个对象时触发一个事件。你可以使用 resolving 方法监听这个事件:

$this->app->resolving(function ($object, $app) {
     
    // 当容器解析任何类型的对象时调用...
});

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
     
    // 当容器解析类型为「HelpSpot\API」的对象时调用...
});

resolving方法添加一个回调到Container类的回调数组中。如果该方法只有一个闭包函数作为参数,该闭包函数将被添加globalResolvingCallbacks属性数组中;如果该方法指明了监听解析类的名称或接口名称,第一个参数将作为键名,第二个闭包函数参数作为键值将被添加到resolvingCallbacks数组中。

    /**
     * Register a new resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function resolving($abstract, Closure $callback = null)
    {
     
        if (is_string($abstract)) {
     
            $abstract = $this->getAlias($abstract);
        }

        if (is_null($callback) && $abstract instanceof Closure) {
     
            $this->globalResolvingCallbacks[] = $abstract;
        } else {
     
            $this->resolvingCallbacks[$abstract][] = $callback;
        }
    }

在容器解析对象过程中,resolve方法内调用fireResolvingCallbacks触发添加的回调函数。

    /**
     * Fire all of the resolving callbacks.
     *
     * @param  string  $abstract
     * @param  mixed   $object
     * @return void
     */
    protected function fireResolvingCallbacks($abstract, $object)
    {
     
        $this->fireCallbackArray($object, $this->globalResolvingCallbacks);

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
        );

        $this->fireAfterResolvingCallbacks($abstract, $object);
    }

    /**
     * Fire all of the after resolving callbacks.
     *
     * @param  string  $abstract
     * @param  mixed   $object
     * @return void
     */
    protected function fireAfterResolvingCallbacks($abstract, $object)
    {
     
        $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
        );
    }

    /**
     * Get all callbacks for a given type.
     *
     * @param  string  $abstract
     * @param  object  $object
     * @param  array   $callbacksPerType
     *
     * @return array
     */
    protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
    {
     
        $results = [];

        foreach ($callbacksPerType as $type => $callbacks) {
     
            if ($type === $abstract || $object instanceof $type) {
     
                $results = array_merge($results, $callbacks);
            }
        }

        return $results;
    }

    /**
     * Fire an array of callbacks with an object.
     *
     * @param  mixed  $object
     * @param  array  $callbacks
     * @return void
     */
    protected function fireCallbackArray($object, array $callbacks)
    {
     
        foreach ($callbacks as $callback) {
     
            $callback($object, $this);
        

你可能感兴趣的:(laravel,php,laravel)