laravel框架容器管理
来自http://www.cnblogs.com/chy1000/p/7072936.html
本文面向php语言的laravel框架的用户,介绍一些laravel框架里面容器管理方面的使用要点。文章很长,但是内容应该很有用,希望有需要的朋友能看到。php经验有限,不到位的地方,欢迎帮忙指正。
这个文件在每一次请求到达laravel框架都会执行,所创建的$app即是laravel框架的应用程序实例,它在整个请求生命周期都是唯一的。laravel提供了很多服务,包括认证,数据库,缓存,消息队列等等,$app作为一个容器管理工具,负责几乎所有服务组件的实例化以及实例的生命周期管理。这种方式能够很好地对代码进行解耦,使得应用程序的业务代码不必操心服务组件的对象从何而来,当需要一个服务类来完成某个功能的时候,仅需要通过容器解析出该类型的一个实例即可。从最终的使用方式来看,laravel容器对服务实例的管理主要包括以下几个方面:
- 服务的绑定与解析
- 服务提供者的管理
- 别名的作用
- 依赖注入
弄清这几个方面的思想, 以及laravel容器的实现机制,就能熟练掌握laravel容器的管理。
文件里面,这个文件定义了很多help函数,并且会通过composer自动加载到项目中。所以,在参与http请求处理的任何代码位置都能够访问其中的函数,比如app()。
2)通过App这个Facade
<?php
Route::get('/', function () {
dd(App::basePath());
return '';
});
通过App这个Facade拿容器实例的方式,跟上面不同的是,不能把App先赋给一个变量,然后通过变量来调用容器的方法。这是因为App相当于只是一个类名,我们不能把一个类名复制一个变量。$app = App;不是一个合法的可执行的语句,而$app = app();却是一个合法的可执行的语句,因为它后面有app(),表示函数调用。App::basePath();也是一个合法的语句,它就是在调用类的静态方法。
再补充2点:
第一点: Facade是laravel框架里面比较特殊的一个特性,每个Facade都会与容器里面的一个实例对象关联,我们可以直接通过Facade类静态方法调用的形式来调用它关联的实例对象的方法。比如App这个Facade,调用App::basePath()的时候,实际相当于app()->basePath()。这个底层机制也是依赖于php语言的特性才能实现的,需要在每一个Facade里面,设定一个静态成员并关联到一个服务的实例对象,当调用Facade类的静态方法的时候,解析出调用的方法名,再去调用关联的服务实例的同名方法,最后把结果返回。我认为理解Facade能起到什么作用就够了,不一定要深究到它底层去了解实现的细节,毕竟在实际的开发中,不用Facade,也完全不影响laravel框架的使用。另外在实际编码中,要自定义一个Facade也非常容易,只要继承laravel封装的Facade基类即可:
<?php
namespace ThirdProviders\CasServer\Facades;
use Illuminate\Support\Facades\Facade;
use ThirdProviders\CasServer\CasServerManager;
class CasServer extends Facade
{
protected static function getFacadeAccessor()
{
return CasServerManager::class;
}
}
实现Facade基类的getFacadeAccessor方法,laravel框架就知道这个Facade类该与哪个服务实例关联起来了。实际上这个getFacadeAccess方法,返回的名称就是后面要介绍的服务绑定名称。在laravel容器里面,一个服务实例,都会有一个固定的绑定名称,通过这个名称就能找到这个实例。所以为啥Facade类只要返回服务绑定名称即可。
我们可以看看App这个Facade类的代码:
<?php
namespace Illuminate\Support\Facades;
/**
* @see \Illuminate\Foundation\Application
*/
class App extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'app';
}
}
它的getFacadeAccessor返回的就是一个字符串“app”,这个app就是laravel容器自己绑定自己时用的名称。
第二点: 从上一点最后App这个Facade的源码可以看出,App这个Facade的全类名其实是:Illuminate\Support\Facades\App,那为什么我们在代码里面能够直接通过App这个简短的名称就能访问到呢:
<?php
Route::get('/', function () {
dd(App::basePath());
return '';
});
你看以上代码完全没有用到use或者完全限定的方式来使用Illuminate\Support\Facades\App。实际上App跟Illuminate\Support\Facades\App是完全等价的,只不过App比Illuminate\Support\Facades\App要简短很多,而且不需要use,所以用起来方便,那么它是怎么实现的?这跟laravel容器配置的别名有关系,在config/app.php中,有一节aliases专门用来配置一些类型的别名:
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class
],
然后在laravel框架处理请求过程中,会通过Illuminate\Foundation\Bootstrap\RegisterFacades这个类来注册这些别名到全局环境里面:
<?php
namespace Illuminate\Foundation\Bootstrap;
use Illuminate\Support\Facades\Facade;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Contracts\Foundation\Application;
class RegisterFacades
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance($app->make('config')->get('app.aliases', []))->register();
}
}
所以我们才能直接通过别名,代替完整的类型名做同样的访问功能。如果你自己写了一些类,名称很长,并且在代码里面用的特别多,也可以考虑配置到config/app.php别名里面去,laravel会帮我们注册。
3)另外一种方式拿到laravel容器实例就是在服务提供者里面直接使用$this->app
服务提供者后面还会介绍,现在只是引入。因为服务提供者类都是由laravel容器实例化的,这些类都继承自Illuminate\Support\ServiceProvider,它定义了一个实例属性$app:
laravel在实例化服务提供者的时候,会把laravel容器实例注入到这个$app上面。所以我们在服务提供者里面,始终能通过$this->$app访问到laravel容器实例,而不需要再使用app()函数或者App Facade了。
从这个结构可以看出,laravel容器实例上包含了很多的数组,其中红框部分的数组,从名字也可以猜测出它们跟后面要介绍的服务,服务提供者与服务别名之间的联系。理清这几个数组的存储结构,自然就明白了laravel容器如何管理服务。
服务绑定时的第一个参数就是服务的绑定名称。服务绑定完成后,容器会把这个服务的绑定记录存储到实例属性bindings里面:
这个bindings里面的每条记录,代表一个服务绑定。它的key值就是服务的绑定名称,value值也是一个数组,这个数组的concrete属性就是服务绑定时产生的匿名函数,也就是闭包;另外一个参数表示这个服务在多次解析的时候,是否只返回第一次解析得到的对象。这个参数在介绍服务绑定方法时会再继续介绍。
接下来看看服务绑定的几种方法及区别:
a. 通过bind方法
app()->bind('service', function(){
return new Service();
},true);
bind是laravel服务绑定的底层方法,它的签名是:
第一个参数服务绑定名称,第二个参数服务绑定的结果,第三个参数就表示这个服务是否在多次解析的时候,始终返回第一次解析出的实例。它的默认值是false,意味着这样的服务在每次解析的时候都会返回一个新的实例。它的值与bindings里面服务绑定记录value数组里面的share属性是对应的。
b. 通过singleton方法
举例略。它跟bind的区别在于,它始终是以shared=true的形式进行服务绑定,这是因为它的源码是这样的:
c. 通过数组的形式
app()['service'] = function(){
return new Service();
};
为什么可以直接把容器实例直接当成数组来用呢,这是因为容器实现了php的ArrayAccess接口:
/**
* Set the value at a given offset.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function offsetSet($key, $value)
{
// If the value is not a Closure, we will make it one. This simply gives
// more "drop-in" replacement functionality for the Pimple which this
// container's simplest functions are base modeled and built after.
if (! $value instanceof Closure) {
$value = function () use ($value) {
return $value;
};
}
$this->bind($key, $value);
}
所以实际上以上这种数组形式的绑定实际上相当于没有第三个参数的bind方法。
再来看服务的解析。上面的内容都是在说明把如何获取服务实例的方式绑定到容器,那么如何从容器获取到需要的服务实例呢?这个过程就是服务解析,在laravel里面通过make方法来完成服务的解析:
$service= app()->make('service');
这个方法接收两个参数,第一个是服务的绑定名称和服务绑定名称的别名,如果是别名,那么就会根据服务绑定名称的别名配置,找到最终的服务绑定名称,然后进行解析;第二个参数是一个数组,最终会传递给服务绑定产生的闭包。
我们可以通过make的源码理解服务解析的逻辑,这个是Illuminate\Container\Container类中的make方法源码,laravel的容器实例是Illuminate\Foundation\Application类的对象,这个类继承了Illuminate\Container\Container,这里暂时只展示Illuminate\Container\Container类中的make方法的代码,先不涉及Illuminate\Foundation\Application类的make方法,因为后者覆盖了Illuminate\Container\Container类中的make方法,加了一些服务提供者的逻辑,所以这里先不介绍它。其实前面的很多源码也都是从Illuminate\Container\Container中拿出来的,不过那些代码Application没有覆盖,不影响内容的介绍。
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($this->normalize($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])) {
return $this->instances[$abstract];
}
$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, $parameters);
} else {
$object = $this->make($concrete, $parameters);
}
// 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)) {
$this->instances[$abstract] = $object;
}
$this->fireResolvingCallbacks($abstract, $object);
$this->resolved[$abstract] = true;
return $object;
}
从这个源码可以看到:
a. 在解析一个服务的时候,它会先尝试把别名转换成有效的服务绑定名称;
b. 如果这个服务是一个shared为true的服务绑定,且之前已经做过解析的话,就会直接返回之前已经解析好的对象;
c. 如果这个服务是一个shared为true的服务绑定,并且是第一次解析的话,就会把已解析的对象存入到instances这个容器属性里面去,也就是说只有shared为true的服务绑定,在解析的时候才会往instances属性里面存入记录,否则不会存入;
d. 解析完毕,还会在容器的resolved属性里面存入一条记录,表示这个服务绑定解析过;
e. resolved,instances数组的key值跟bindings数组的key值一样,都是服务绑定名称;
f. 服务绑定的shared属性在整个服务绑定生命周期内都是不能更改的。
服务的解析也有多种形式,常用的有:
a. make方法
b. 数组形式
app()['service'];
这个的原理还是跟容器实现了ArrayAccess的接口有关系:
public function offsetGet($key)
{
return $this->make($key);
}
所以数组形式的访问跟不使用第二个参数的make方法形式是一样的。
c. app($service)的形式
app('service');
看了app这个help函数的源码就明白了:
function app($make = null, $parameters = [])
{
if (is_null($make)) {
return Container::getInstance();
}
return Container::getInstance()->make($make, $parameters);
}
原来app这个函数在第一个参数为空的时候,返回的是容器实例本身。在有参数的时候等价于调用容器实例的make方法。
以上就是服务绑定与解析的主要内容,涉及的要点较多,希望描述的比较清楚。
其实你放在哪都可以,不过得告诉laravel你的服务提供者在哪,laravel才会帮你注册。怎么告诉它,后面还有介绍。
2)在这个举例里面,可以看到有一个register方法,这个方法是ServiceProvider里面定义的。自定义的时候,需要重写它。这个方法就是用来绑定服务的。你可以在这个服务里面,根据需要加入任意数量的服务绑定。前面要介绍过,在服务提供者里面,始终能通过$this->app拿到容器实例,所以上面的举例中,我们直接用这种方式来完成服务绑定。这个方法是怎么完成服务绑定的呢?因为当laravel找到这个服务提供者的类以后,就会初始化这个服务提供者类,得到一个服务提供者的对象,然后调用它的register方法,自然它里面的所有服务绑定代码就都会执行了。
laravel初始化自定义服务提供者的源码是:
public function registerConfiguredProviders()
{
$manifestPath = $this->getCachedServicesPath();
(new ProviderRepository($this, new Filesystem, $manifestPath))
->load($this->config['app.providers']);
}
这个代码是在Illuminate\Foundation\Application的源码里面拿出来的,从中你能看到laravel会把所有的自定义服务提供者都注册进来。这个注册的过程其实就是前面说的实例化服务提供者的类,并调用register方法的过程。
3). 从上一步的源码也能看到,laravel加载自定义服务提供者的时候,实际是从config/app.php这个配置文件里面的providers配置节找到所有要注册的服务提供者的。
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
//
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Xavrsl\Cas\CasServiceProvider::class,
ThirdProviders\CasServer\CasServerProvider::class
],
所以你如果自己写了一个服务提供者,那么只要配置到这里面,laravel就会自动帮你注册它了。
4)除了register方法,服务提供者里面还有一个boot方法,这个boot方法,会在所有的服务提供者都注册完成之后才会执行,所以当你想在服务绑定完成之后,通过容器解析出其它服务,做一些初始化工作的时候,那么就可以这些逻辑写在boot方法里面。因为boot方法执行的时候,所有服务提供者都已经被注册完毕了,所以在boot方法里面能够确保其它服务都能被解析出来。
5)前面说的服务提供者的情况,在laravel应用程序初始化的时候,就会去注册服务提供者,调用register方法。但是还有一种需求,你可能需要在真正用到这个服务提供者绑定的服务的时候,才会去注册这个服务提供者,以减少不必要的注册处理,提高性能。这也是延迟处理的一种方式。那么这种服务提供者该怎么定义呢?
其实最前面的这个举例已经告诉你了,只要定义一个$defer的实例属性,并把这个实例属性设置为true,然后添加一个provides的实例方法即可。这两个成员都是ServiceProvider基类里面定义好的,自定义的时候,只是覆盖而已。
在基类中,$defer的默认值是false,表示这个服务提供者不需要延迟注册。provides方法,只要简单的返回这个服务提供register方法里面,注册的所有服务绑定名称即可。
延迟注册的服务提供者的机制是:
- 当laravel初始化服务提供者的实例后,如果发现这个服务提供者的$defer属性为true,那么就不会去调用它的register方法
- 当laravel解析一个服务的时候,如果发现这个服务是由一个延迟服务提供的(它怎么知道这个服务是延迟服务提供的,是provides方法告诉它的),那么就会先把这个延迟服务提供者先注册,再去解析。这个可以看看Illuminate\Foundation\Application的make方法就清楚了:
public function make($abstract, array $parameters = []) { $abstract = $this->getAlias($abstract); if (isset($this->deferredServices[$abstract])) { $this->loadDeferredProvider($abstract); } return parent::make($abstract, $parameters); }
6)还记得容器实例结构上几个带有providers名称的属性数组吧:
在了解以上provider的机制后,这几个数组的作用也就比较清晰了。其中serviceProviders用来存放所有已经注册完毕的服务提供者:
loadedProviders跟serviceProviders的作用类似,只是存储的记录形式不同:
deferredProviders用来存储所有的延迟注册的服务提供者:
跟前面两个不同的是,deferredProviders存储的记录的key值并不是服务提供者的类型名称,而是服务提供者的provides返回数组里面的名称。并且如果一个服务提供者的provides里面返回了多个服务绑定名称的话,那么deferredProviders里面就会存多条记录:
这样是方便根据服务绑定名称,找到对应的服务提供者,并完成注册。当服务的解析的时候,会先完成延迟类型的服务提供者的注册,注册完毕,这个服务绑定名称在deferredProviders对应的那条记录就会删除掉。不过如果一个服务提供者provides了多个服务绑定名称,解析其中一个服务的时候,只移除该名称对应的deferredProviders记录,而不是所有。
7)服务提供者还有一个小问题值的注意,由于php是一门基本语言,在处理请求的时候,都会从入口文件把所有php都执行一遍。为了性能考虑,laravel会在第一次初始化的时候,把所有的服务提供者都缓存到bootstrap/cache/services.php文件里面,所以有时候当你改了一个服务提供者的代码以后,再刷新不一定能看到期望的效果,这有可能就是因为缓存所致。这时把services.php删掉就能看到你要的效果了。
你看刚才举例中的别名就已经添加到这个数组里面。这个数组里面每条记录的key值都是别名。但是value有可能是服务绑定名称,也有可能是另外一个别名。这是因为别名是可以递归的。
2)别名支持递归
也就是说,可以对别名再指定别名:
3)别名如何应用于服务解析
在解析服务的时候,会先确定这个服务名称是否为一个别名(只要看看在aliases数组里是否存在记录即可),如果不是别名,直接用这个服务名称进行解析。如果这个服务名称是一个别名,那么就会通过调用的方式,找到最终的服务名称:
如下所有的服务解析都是等价的:
app('alias_c');
app('alias_b');
app('alias_a');
app('service1');
4)另外一种指定别名的方式
可以在服务绑定的时候,进行别名的指定。只要按照如下的方式进行绑定即可:
app()->singleton(['service1' => 'alias'], function(){
new CasServerManager();
});
也就是把服务绑定名称换成数组形式而已。数组记录的key值就是服务名称,value值就是别名。
当一个类需要某个服务类型的实例时,不需要自己去创造这个服务的实例,只要告诉容器,它需要的实例类型即可,然后容器会根据这个类型, 解析出满足该类型的服务。如何根据参数类型解析出该参数类型的服务实例呢?其实就是根据参数类型的类型名称进行解析得到的,所以依赖注入能够成功的前提是根据参数类型的名称,能够成功地解析到一个服务对象。以上之所以能够通过Illuminate\Contracts\Foundation\Application 这个名称解析到服务,那是因为在容器实例aliases数组里面有一条Illuminate\Contracts\Foundation\Application 的别名记录:
也就是说Illuminate\Contracts\Foundation\Application 实际上是app这个服务绑定名称的一个别名,所以laravel在解析Illuminate\Contracts\Foundation\Application的时候,就能得到对应的服务实例了。
这些别名属于laravel容器核心的别名,在laravel初始化的时候会被注册:
public function registerCoreContainerAliases()
{
$aliases = [
'app' => ['Illuminate\Foundation\Application', 'Illuminate\Contracts\Container\Container', 'Illuminate\Contracts\Foundation\Application'],
'auth' => ['Illuminate\Auth\AuthManager', 'Illuminate\Contracts\Auth\Factory'],
'auth.driver' => ['Illuminate\Contracts\Auth\Guard'],
'blade.compiler' => ['Illuminate\View\Compilers\BladeCompiler'],
'cache' => ['Illuminate\Cache\CacheManager', 'Illuminate\Contracts\Cache\Factory'],
'cache.store' => ['Illuminate\Cache\Repository', 'Illuminate\Contracts\Cache\Repository'],
'config' => ['Illuminate\Config\Repository', 'Illuminate\Contracts\Config\Repository'],
'cookie' => ['Illuminate\Cookie\CookieJar', 'Illuminate\Contracts\Cookie\Factory', 'Illuminate\Contracts\Cookie\QueueingFactory'],
'encrypter' => ['Illuminate\Encryption\Encrypter', 'Illuminate\Contracts\Encryption\Encrypter'],
'db' => ['Illuminate\Database\DatabaseManager'],
'db.connection' => ['Illuminate\Database\Connection', 'Illuminate\Database\ConnectionInterface'],
'events' => ['Illuminate\Events\Dispatcher', 'Illuminate\Contracts\Events\Dispatcher'],
'files' => ['Illuminate\Filesystem\Filesystem'],
'filesystem' => ['Illuminate\Filesystem\FilesystemManager', 'Illuminate\Contracts\Filesystem\Factory'],
'filesystem.disk' => ['Illuminate\Contracts\Filesystem\Filesystem'],
'filesystem.cloud' => ['Illuminate\Contracts\Filesystem\Cloud'],
'hash' => ['Illuminate\Contracts\Hashing\Hasher'],
'translator' => ['Illuminate\Translation\Translator', 'Symfony\Component\Translation\TranslatorInterface'],
'log' => ['Illuminate\Log\Writer', 'Illuminate\Contracts\Logging\Log', 'Psr\Log\LoggerInterface'],
'mailer' => ['Illuminate\Mail\Mailer', 'Illuminate\Contracts\Mail\Mailer', 'Illuminate\Contracts\Mail\MailQueue'],
'auth.password' => ['Illuminate\Auth\Passwords\PasswordBrokerManager', 'Illuminate\Contracts\Auth\PasswordBrokerFactory'],
'auth.password.broker' => ['Illuminate\Auth\Passwords\PasswordBroker', 'Illuminate\Contracts\Auth\PasswordBroker'],
'queue' => ['Illuminate\Queue\QueueManager', 'Illuminate\Contracts\Queue\Factory', 'Illuminate\Contracts\Queue\Monitor'],
'queue.connection' => ['Illuminate\Contracts\Queue\Queue'],
'queue.failer' => ['Illuminate\Queue\Failed\FailedJobProviderInterface'],
'redirect' => ['Illuminate\Routing\Redirector'],
'redis' => ['Illuminate\Redis\Database', 'Illuminate\Contracts\Redis\Database'],
'request' => ['Illuminate\Http\Request', 'Symfony\Component\HttpFoundation\Request'],
'router' => ['Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar'],
'session' => ['Illuminate\Session\SessionManager'],
'session.store' => ['Illuminate\Session\Store', 'Symfony\Component\HttpFoundation\Session\SessionInterface'],
'url' => ['Illuminate\Routing\UrlGenerator', 'Illuminate\Contracts\Routing\UrlGenerator'],
'validator' => ['Illuminate\Validation\Factory', 'Illuminate\Contracts\Validation\Factory'],
'view' => ['Illuminate\View\Factory', 'Illuminate\Contracts\View\Factory'],
];
foreach ($aliases as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
依赖注入更多地用在接口编程当中,就像上面的举例类似。再看一个自定义的例子:
<?php
interface Inter{
public function method();
}
class InterImpl implements Inter{
public function method(){
//
}
}
class Service{
protected $inter;
public function __construct(Inter $inter)
{
$this->inter = $inter;
}
}
app()->singleton(Inter::class,InterImpl::class);
app()->singleton(Service::class);
Route::get('/', function () {
dd(app(Service::class));
return '';
});
按接口进行编程,像Service这种业务类,只需要声明自己需要一个Inter类型的实例即可。接口的好处在于解耦,将来要更换一种Inter的实现,不需要改Service的代码,只需要在实例化Service的时候,传入另外一个Inter的实例即可。有了依赖注入以后,也不用改Service实例化的代码,只要把Inter这个服务类型,重新做一个绑定,绑定到另外一个实现即可。
app()->singleton(Inter::class,InterImpl2::class);
从源码看出,share方法把服务绑定的闭包再包装了一下,返回一个新的闭包,并且在这个闭包里面,加了一个静态$object变量,它会存储原始闭包第一次解析调用后的结果,并在后续解析中直接返回,从而保证这个服务的实例只有一个。