1. 背景
慣例介紹下容器的背景,回答第一個(gè)問(wèn)題:什么是容器游添?
顧名思義系草,容器即存放東西的地方,里面存放的可以是文本唆涝、數(shù)值找都,甚至是對(duì)象、接口廊酣、回調(diào)函數(shù)能耻。
那通過(guò)容器,解決了什么問(wèn)題呢?
通過(guò)容器最主要解決的就是“解耦” 嚎京、“依賴注入(DI)“嗡贺,從而實(shí)現(xiàn)”控制反轉(zhuǎn)(IoC)“
2. DI
上面將了容器是用來(lái)解決依賴注入的隐解,那到底什么是依賴注入呢鞍帝?我們以下面的例子來(lái)說(shuō)明下:
我們假設(shè)有一個(gè)訂單,在構(gòu)造函數(shù)中我們新建了OrderRepository
煞茫,通過(guò)倉(cāng)庫(kù)我們就可以對(duì)訂單進(jìn)行持久化了帕涌,但是突然有一天,我們想把訂單的存儲(chǔ)從數(shù)據(jù)庫(kù)換到redis续徽,我們這時(shí)候就必須改訂單的構(gòu)造函數(shù)蚓曼,將OrderRepository
換為OrderRedisRepository
,而且可能兩者的接口還不一樣钦扭,改動(dòng)成本非常大纫版。如果哪天我們又想將存儲(chǔ)換到mongodb,那我們又得改Order
的構(gòu)造函數(shù)客情,這個(gè)時(shí)候其弊,我們可以定義一個(gè)接口Repository
,而Order的構(gòu)造函數(shù)接受Repository
作為參數(shù)膀斋,
class Order {
/**
* @type Repository
*/
private $repository;
/**
* Order constructor.
*
* @param Repository $repository
*/
public function __construct(Repository $repository)
{
// $this->repository = new OrderMysqlRepository();
// $this->repository = new OrderRedisRepository();
$this->repository = $repository;
}
}
這樣客戶端在使用上就變成
$repository = new OrderMysqlRepository();
$order = new Order($repository);
上面就是依賴注入了梭伐,我們通過(guò)構(gòu)造函數(shù)傳參的方式將$repository
注入到了Order
中。
了解了依賴注入仰担,下面就到了我們今天的重點(diǎn)依賴反轉(zhuǎn)糊识。
3. 依賴反轉(zhuǎn)
上面客戶端在使用的時(shí)候,還是需要手動(dòng)的創(chuàng)建OrderMysqlRepository
摔蓝,有沒(méi)有可能將這個(gè)創(chuàng)建的邏輯也從客戶端抽離出來(lái)呢赂苗?看下面的代碼
class Container
{
protected $binds;
protected $instances;
public function bind($abstract, $concrete)
{
if ($concrete instanceof \Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
array_unshift($parameters, $this);
return call_user_func_array($this->binds[$abstract], $parameters);
}
}
上面就是一個(gè)簡(jiǎn)單的容器,在使用上
public function testContainer()
{
$container = new Container();
$container->bind('order',function(Container $c,$repository){
return new Order($c->make($repository));
});
$container->bind('Repository',new OrderRedisRepository);
$order = $container->make('order',['Repository']);
}
以上就是一個(gè)基本簡(jiǎn)單可用的Ioc容器了贮尉。
我們可以看到IoC核心就是通過(guò)事先將一些代碼片段注冊(cè)到容器中拌滋,當(dāng)我們需要實(shí)例化類的時(shí)候,通過(guò)容器绘盟,自動(dòng)的將對(duì)象需要的參數(shù)實(shí)例化出來(lái)鸠真,并注入進(jìn)去。
4. Laravel中的容器
Laravel中容器共有15個(gè)方法龄毡,簡(jiǎn)單分類了下
4.1 注冊(cè)
4.1.1 bind
先來(lái)看下注冊(cè)吠卷,Laravel的容器支持好多種注冊(cè)方式,先看最常用的bind
沦零,其函數(shù)簽名是:
public function bind($abstract, $concrete = null, $shared = false);
看到簽名中有3個(gè)參數(shù)祭隔,在函數(shù)內(nèi)部經(jīng)過(guò)各種操作后,最終落地到存儲(chǔ)上,形式是:
$bindings = [
'abstract' => [
'concrete' => $concrete,
'shared' => $shared;
],
];
bind
在注冊(cè)上疾渴,像之前提到過(guò)的千贯,可以注冊(cè)文本、數(shù)值搞坝,甚至是對(duì)象搔谴、接口、回調(diào)函數(shù)桩撮,下面就每種形式給出測(cè)試敦第,
先看閉包形式:
public function testClosureResolution()
{
$container = new Container;
$container->bind('name', function () {
return 'Taylor';
});
// dd($container);
$this->assertEquals('Taylor', $container->make('name'));
}
上面為了測(cè)試,通過(guò)dd
可以打印出好container來(lái)店量,我們看到
Illuminate\Container\Container {#20
#resolved: []
#bindings: array:1 [
"name" => array:2 [
"concrete" => Closure {#21
class: "LaravelContainerTest"
}
"shared" => false
]
]
#instances: []
#aliases: []
#extenders: []
#tags: []
#buildStack: []
+contextual: []
#reboundCallbacks: []
#globalResolvingCallbacks: []
#globalAfterResolvingCallbacks: []
#resolvingCallbacks: []
#afterResolvingCallbacks: []
}
上面是container的內(nèi)部芜果,經(jīng)過(guò)bind后,里面的bindings
多了我們注冊(cè)過(guò)的name
融师,下一步注冊(cè)過(guò)了右钾,就應(yīng)該要調(diào)用make
實(shí)例化出來(lái),調(diào)用make
后旱爆,container中resolved多個(gè)key
#resolved: array:1 [
"name" => true
]
在實(shí)現(xiàn)make的時(shí)候舀射,通過(guò)判斷是否是閉包來(lái)判斷,如果是閉包疼鸟,則直接調(diào)用后控,否則通過(guò)反射機(jī)制實(shí)例化出來(lái)
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}
$reflector = new ReflectionClass($concrete);
4.1.2 instance
instance是將我們已經(jīng)實(shí)例化出來(lái)的對(duì)象、文本等注冊(cè)進(jìn)入容器空镜,使用方法如下
public function testSimpleInstance()
{
$c = new Container();
$name = 'zhuanxu';
$c->instance('name',$name);
$this->assertEquals($name,$c->make('name'));
}
instance方法將其寫入到instances: []
中
4.1.3 singleton
$container = new Container;
$container->singleton('ContainerConcreteStub');
$var1 = $container->make('ContainerConcreteStub');
$var2 = $container->make('ContainerConcreteStub');
$this->assertSame($var1, $var2);
singleton是對(duì)bind的簡(jiǎn)單封裝
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
4.1.4 alias
public function testAliases()
{
$container = new Container;
$container['foo'] = 'bar';
$container->alias('foo', 'baz');
$container->alias('baz', 'bat');
$this->assertEquals('bar', $container->make('foo'));
$this->assertEquals('bar', $container->make('baz'));
$this->assertEquals('bar', $container->make('bat'));
$container->bind(['bam' => 'boom'], function () {
return 'pow';
});
$this->assertEquals('pow', $container->make('bam'));
$this->assertEquals('pow', $container->make('boom'));
$container->instance(['zoom' => 'zing'], 'wow');
$this->assertEquals('wow', $container->make('zoom'));
$this->assertEquals('wow', $container->make('zing'));
}
alias函數(shù)是通過(guò)起別名的方式來(lái)讓容器make
4.1.5 share
share
是通過(guò)閉包的形式浩淘,加上關(guān)鍵字static
實(shí)現(xiàn)的
public function share(Closure $closure)
{
return function ($container) use ($closure) {
static $object;
if (is_null($object)) {
$object = $closure($container);
}
return $object;
};
}
4.1.6 extend
extend是在當(dāng)原來(lái)的容器實(shí)例化出來(lái)后,可以對(duì)其進(jìn)行擴(kuò)展
public function testExtendInstancesArePreserved()
{
$container = new Container;
$container->bind('foo', function () {
$obj = new StdClass;
$obj->foo = 'bar';
return $obj;
});
$obj = new StdClass;
$obj->foo = 'foo';
$container->instance('foo', $obj);
$container->extend('foo', function ($obj, $container) {
$obj->bar = 'baz';
return $obj;
});
$container->extend('foo', function ($obj, $container) {
$obj->baz = 'foo';
return $obj;
});
$this->assertEquals('foo', $container->make('foo')->foo);
$this->assertEquals('baz', $container->make('foo')->bar);
$this->assertEquals('foo', $container->make('foo')->baz);
}
4.2 實(shí)例化
4.2.1 call
call直接調(diào)用函數(shù)吴攒,自動(dòng)注入依賴
public function testCallWithDependencies()
{
$container = new Container;
$result = $container->call(function (StdClass $foo, $bar = []) {
return func_get_args();
});
$this->assertInstanceOf('stdClass', $result[0]);
$this->assertEquals([], $result[1]);
$result = $container->call(function (StdClass $foo, $bar = []) {
return func_get_args();
}, ['bar' => 'taylor']);
$this->assertInstanceOf('stdClass', $result[0]);
$this->assertEquals('taylor', $result[1]);
/*
* Wrap a function...
*/
$result = $container->wrap(function (StdClass $foo, $bar = []) {
return func_get_args();
}, ['bar' => 'taylor']);
$this->assertInstanceOf('Closure', $result);
$result = $result();
$this->assertInstanceOf('stdClass', $result[0]);
$this->assertEquals('taylor', $result[1]);
}
今天就先講到這张抄,有用的地方再去看的。