Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具。依赖注入这个花俏名词实质上是指:类的依赖项通过构造函数,或者某些情况下通过「setter」方法「注入」到类中。
Laravel服务容器的功能多由Illuminate\Container\Container
类提供,由于Illuminate\Foundation\Application
类继承了该类,在使用服务容器时可以通过Illuminate\Foundation\Application
类实例进行方法调用。
在服务提供器中,你可以通过
$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
方法内使用Application
的make
方法进行解析。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
内查找aliases
、abstractAliases
、contextual
、bindings
多个属性数组,找到最终要创建的类名,调用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);