背景
惯例介绍下容器的背景,回答第一个问题:什么是容器?
顾名思义,容器即存放东西的地方,里面存放的可以是文本、数值,甚至是对象、接口、回调函数。
那通过容器,解决了什么问题呢?
通过容器最主要解决的就是“解耦”、“依赖注入(DI)”,从而实现“控制反转(IoC)”
DI
上面将了容器是用来解决依赖注入的,那到底什么是依赖注入呢?我们以下面的例子来说明下:
我们假设有一个订单,在构造函数中我们新建了OrderRepository
,通过仓库我们就可以对订单进行持久化了,但是突然有一天,我们想把订单的存储从数据库换到redis
,我们这时候就必须改订单的构造函数,将OrderRepository
换为OrderRedisRepository
,而且可能两者的接口还不一样,改动成本非常大。如果哪天我们又想将存储换到mongodb
,那我们又得改Order
的构造函数,这个时候,我们可以定义一个接口Repository
,而Order
的构造函数接受Repository
作为参数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Order {
private $repository;
public function __construct(Repository $repository) {
$this->repository = $repository; } }
|
这样客户端在使用上就变成
1 2
| $repository = new OrderMysqlRepository(); $order = new Order($repository);
|
上面就是依赖注入了,我们通过构造函数传参的方式将$repository
注入到了Order
中。
了解了依赖注入,下面就到了我们今天的重点依赖反转。
依赖反转
上面客户端在使用的时候,还是需要手动的创建OrderMysqlRepository,有没有可能将这个创建的逻辑也从客户端抽离出来呢?看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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); } }
|
上面就是一个简单的容器,在使用上
1 2 3 4 5 6 7 8 9
| 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']); }
|
以上就是一个基本简单可用的Ioc容器了。
我们可以看到IoC核心就是通过事先将一些代码片段注册到容器中,当我们需要实例化类的时候,通过容器,自动的将对象需要的参数实例化出来,并注入进去。
Laravel中的容器
Laravel中容器共有15个方法,简单分类了下
注册
bind
先来看下注册,Laravel的容器支持好多种注册方式,先看最常用的bind,其函数签名是:
1
| public function bind($abstract, $concrete = null, $shared = false);
|
看到签名中有3个参数,在函数内部经过各种操作后,最终落地到存储上,形式是:
1 2 3 4 5 6
| $bindings = [ 'abstract' => [ 'concrete' => $concrete, 'shared' => $shared; ], ];
|
bind在注册上,像之前提到过的,可以注册文本、数值,甚至是对象、接口、回调函数,下面就每种形式给出测试,
先看闭包形式:
1 2 3 4 5 6 7 8 9
| public function testClosureResolution() { $container = new Container; $container->bind('name', function () { return 'Taylor'; });
$this->assertEquals('Taylor', $container->make('name')); }
|
上面为了测试,通过dd
可以打印出好container
来,我们看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Illuminate\Container\Container { "name" => array:2 [ "concrete" => Closure { class: "LaravelContainerTest" } "shared" => false ] ] #instances: [] #aliases: [] #extenders: [] #tags: [] #buildStack: [] +contextual: [] #reboundCallbacks: [] #globalResolvingCallbacks: [] #globalAfterResolvingCallbacks: [] #resolvingCallbacks: [] #afterResolvingCallbacks: [] }
|
上面是container
的内部,经过bind
后,里面的bindings
多了我们注册过的name
,下一步注册过了,就应该要调用make
实例化出来,调用make
后,container
中resolved
多个key
在实现make
的时候,通过判断是否是闭包来判断,如果是闭包,则直接调用,否则通过反射机制实例化出来
1 2 3 4 5
| if ($concrete instanceof Closure) { return $concrete($this, $parameters); }
$reflector = new ReflectionClass($concrete);
|
instance
instance
是将我们已经实例化出来的对象、文本等注册进入容器,使用方法如下
1 2 3 4 5 6 7
| public function testSimpleInstance() { $c = new Container(); $name = 'zhuanxu'; $c->instance('name',$name); $this->assertEquals($name,$c->make('name')); }
|
instance方法将其写入到instances: []
中
singleton
1 2 3 4 5 6
| $container = new Container; $container->singleton('ContainerConcreteStub');
$var1 = $container->make('ContainerConcreteStub'); $var2 = $container->make('ContainerConcreteStub'); $this->assertSame($var1, $var2);
|
singleton
是对bind
的简单封装
1 2 3 4
| public function singleton($abstract, $concrete = null) { $this->bind($abstract, $concrete, true); }
|
alias
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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
函数是通过起别名的方式来让容器make
share
share
是通过闭包的形式,加上关键字static
实现的
1 2 3 4 5 6 7 8 9 10 11 12
| public function share(Closure $closure) { return function ($container) use ($closure) { static $object;
if (is_null($object)) { $object = $closure($container); }
return $object; }; }
|
extend
extend
是在当原来的容器实例化出来后,可以对其进行扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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); }
|
实例化
call
call直接调用函数,自动注入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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]);
$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]); }
|