Zihao

Make small but daily progress

0%

Laravel之容器

背景

惯例介绍下容器的背景,回答第一个问题:什么是容器?

顾名思义,容器即存放东西的地方,里面存放的可以是文本、数值,甚至是对象、接口、回调函数。

那通过容器,解决了什么问题呢?

通过容器最主要解决的就是“解耦”、“依赖注入(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 {

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

这样客户端在使用上就变成

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';
});
// dd($container);
$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 {#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的内部,经过bind后,里面的bindings多了我们注册过的name,下一步注册过了,就应该要调用make实例化出来,调用make后,containerresolved多个key

1
2
3
4
#resolved: array:1 [
"name" => true
]

在实现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]);

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

欢迎关注我的其它发布渠道