首先欢迎关注个人博客: www.leoyang90.cnphp
在前面几个博客中,我详细讲了 Ioc 容器各个功能的使用、绑定的源码、解析的源码,今天这篇博客会详细介绍 Ioc 容器的一些细节,一些特性,以便更好地掌握容器的功能。laravel
注:本文使用的测试类与测试对象都取自 laravel 的单元测试文件src/illuminate/tests/Container/ContainerTest.php数组
instance 和 普通 bind 绑定同样,当从新绑定的时候都会调用 rebind 回调函数,可是有趣的是,对于普通 bind 绑定来讲,rebind 回调函数被调用的条件是当前接口被解析过:闭包
public function testReboundListeners() { unset($_SERVER['__test.rebind']); $container = new Container; $container->rebinding('foo', function () { $_SERVER['__test.rebind'] = true; }); $container->bind('foo', function () { }); $container->make('foo'); $container->bind('foo', function () { }); $this->assertTrue($_SERVER['__test.rebind']); }
因此遇到下面这样的状况,rebinding 的回调函数是不会调用的:app
public function testReboundListeners() { unset($_SERVER['__test.rebind']); $container = new Container; $container->rebinding('foo', function () { $_SERVER['__test.rebind'] = true; }); $container->bind('foo', function () { }); $container->bind('foo', function () { }); $this->assertFalse(isset($_SERVER['__test.rebind'])); }
有趣的是对于 instance 绑定:ide
public function testReboundListeners() { unset($_SERVER['__test.rebind']); $container = new Container; $container->rebinding('foo', function () { $_SERVER['__test.rebind'] = true; }); $container->bind('foo', function () { }); $container->instance('foo', function () { }); $this->assertTrue(isset($_SERVER['__test.rebind'])); }
rebinding 回调函数倒是能够被调用的。其实缘由就是 instance 源码中 rebinding 回调函数调用的条件是 rebound 为真,而普通 bind 函数调用 rebinding 回调函数的条件是 resolved 为真. 目前笔者不是很清楚为何要对 instance 和 bind 区别对待,但愿有大牛指导。函数
为了使得 rebind 回调函数在下一次的绑定中被激活,在 rebind 函数的源码中,若是判断当前对象已经绑定过,那么将会当即解析:单元测试
public function rebinding($abstract, Closure $callback) { $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; if ($this->bound($abstract)) { return $this->make($abstract); } }
单元测试代码:测试
public function testReboundListeners1() { unset($_SERVER['__test.rebind']); $container = new Container; $container->bind('foo', function () { return 'foo'; }); $container->resolving('foo', function () { $_SERVER['__test.rebind'] = true; }); $container->rebinding('foo', function ($container,$object) {//会当即解析 $container['foobar'] = $object.'bar'; }); $this->assertTrue($_SERVER['__test.rebind']); $container->bind('foo', function () { }); $this->assertEquals('bar', $container['foobar']); }
resolving 不只能够针对接口执行回调函数,还能够针对接口实现的类型进行回调函数。this
public function testResolvingCallbacksAreCalledForType() { $container = new Container; $container->resolving('StdClass', function ($object) { return $object->name = 'taylor'; }); $container->bind('foo', function () { return new StdClass; }); $instance = $container->make('foo'); $this->assertEquals('taylor', $instance->name); } public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases() { $container = new Container; $container->alias('StdClass', 'std'); $container->resolving('std', function ($object) { return $object->name = 'taylor'; }); $container->bind('foo', function () { return new StdClass; }); $instance = $container->make('foo'); $this->assertEquals('taylor', $instance->name); }
前面讲过,对于 singleton 绑定来讲,resolving 回调函数仅仅运行一次,只在 singleton 第一次解析的时候才会调用。若是咱们利用 instance 直接绑定类的对象,不须要解析,那么 resolving 回调函数将不会被调用:
public function testResolvingCallbacksAreCalledForSpecificAbstracts() { $container = new Container; $container->resolving('foo', function ($object) { return $object->name = 'taylor'; }); $obj = new StdClass; $container->instance('foo', $obj); $instance = $container->make('foo'); $this->assertFalse(isset($instance->name)); }
extend 用于扩展绑定对象的功能,对于普通绑定来讲,这个函数的位置很灵活:
public function testExtendIsLazyInitialized() { ContainerLazyExtendStub::$initialized = false; $container = new Container; $container->extend('Illuminate\Tests\Container\ContainerLazyExtendStub', function ($obj, $container) { $obj->init(); return $obj; }); $container->bind('Illuminate\Tests\Container\ContainerLazyExtendStub'); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->make('Illuminate\Tests\Container\ContainerLazyExtendStub'); $this->assertTrue(ContainerLazyExtendStub::$initialized); }
public function testExtendIsLazyInitialized() { ContainerLazyExtendStub::$initialized = false; $container = new Container; $container->bind('Illuminate\Tests\Container\ContainerLazyExtendStub'); $container->extend('Illuminate\Tests\Container\ContainerLazyExtendStub', function ($obj, $container) { $obj->init(); return $obj; }); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->make('Illuminate\Tests\Container\ContainerLazyExtendStub'); $this->assertTrue(ContainerLazyExtendStub::$initialized); }
public function testExtendIsLazyInitialized() { ContainerLazyExtendStub::$initialized = false; $container = new Container; $container->bind('Illuminate\Tests\Container\ContainerLazyExtendStub'); $container->make('Illuminate\Tests\Container\ContainerLazyExtendStub'); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->extend('Illuminate\Tests\Container\ContainerLazyExtendStub', function ($obj, $container) { $obj->init(); return $obj; }); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->make('Illuminate\Tests\Container\ContainerLazyExtendStub'); $this->assertTrue(ContainerLazyExtendStub::$initialized); }
能够看出,不管在哪一个位置,extend 扩展都有 lazy 初始化的特色,也就是使用 extend 函数并不会当即起做用,而是要等到 make 解析才会激活。
对于 instance 绑定来讲,暂时 extend 的位置须要位于 instance 以后才会起做用,而且会当即起做用,没有 lazy 的特色:
public function testExtendInstancesArePreserved() { $container = new Container; $obj = new StdClass; $obj->foo = 'foo'; $container->instance('foo', $obj); $container->extend('foo', function ($obj, $container) { $obj->bar = 'baz'; return $obj; }); $this->assertEquals('foo', $container->make('foo')->foo); $this->assertEquals('baz', $container->make('foo')->bar); }
不管扩展对象是 instance 绑定仍是 bind 绑定,extend 都会启动 rebind 回调函数:
public function testExtendReBindingInstance() { $_SERVER['_test_rebind'] = false; $container = new Container; $container->rebinding('foo',function (){ $_SERVER['_test_rebind'] = true; }); $obj = new StdClass; $container->instance('foo',$obj); $container->make('foo'); $container->extend('foo', function ($obj, $container) { return $obj; }); this->assertTrue($_SERVER['_test_rebind']); } public function testExtendReBinding() { $_SERVER['_test_rebind'] = false; $container = new Container; $container->rebinding('foo',function (){ $_SERVER['_test_rebind'] = true; }); $container->bind('foo',function (){ $obj = new StdClass; return $obj; }); $container->make('foo'); $container->extend('foo', function ($obj, $container) { return $obj; }); this->assertFalse($_SERVER['_test_rebind']); }
contextual 绑定不只能够与 bind 绑定合做,相互不干扰,还能够与 instance 绑定相互合做。并且 instance 的位置也很灵活,能够在 contextual 绑定前,也能够在contextual 绑定后:
public function testContextualBindingWorksForExistingInstancedBindings() { $container = new Container; $container->instance('Illuminate\Tests\Container\IContainerContractStub', new ContainerImplementationStub); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf( 'Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl ); }
public function testContextualBindingWorksForNewlyInstancedBindings() { $container = new Container; $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $container->instance('Illuminate\Tests\Container\IContainerContractStub', new ContainerImplementationStub); $this->assertInstanceOf( 'Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl ); }
contextual 绑定也能够在别名上进行,不管赋予别名的位置是 contextual 的前面仍是后面:
public function testContextualBindingDoesntOverrideNonContextualResolution() { $container = new Container; $container->instance('stub', new ContainerImplementationStub); $container->alias('stub', 'Illuminate\Tests\Container\IContainerContractStub'); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectTwo')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf( 'Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->make('Illuminate\Tests\Container\ContainerTestContextInjectTwo')->impl ); $this->assertInstanceOf( 'Illuminate\Tests\Container\ContainerImplementationStub', $container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl ); } public function testContextualBindingWorksOnNewAliasedBindings() { $container = new Container; $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $container->bind('stub', ContainerImplementationStub::class); $container->alias('stub', 'Illuminate\Tests\Container\IContainerContractStub'); $this->assertInstanceOf( 'Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl ); }
目前比较有争议的是下面的状况:
public function testContextualBindingWorksOnExistingAliasedInstances() { $container = new Container; $container->alias('Illuminate\Tests\Container\IContainerContractStub', 'stub'); $container->instance('stub', new ContainerImplementationStub); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('stub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf( 'Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl ); }
因为instance的特性,当别名被绑定到其余对象上时,别名 stub 已经失去了与 IlluminateTestsContainerIContainerContractStub 之间的关系,所以不能使用 stub 代替做上下文绑定。
可是另外一方面:
public function testContextualBindingWorksOnBoundAlias() { $container = new Container; $container->alias('Illuminate\Tests\Container\IContainerContractStub', 'stub'); $container->bind('stub', ContainerImplementationStub::class); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('stub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf( 'Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl ); }
代码只是从 instance 绑定改成 bind 绑定,因为 bind 绑定只切断了别名中的 alias 数组的联系,并无断绝abstractAlias数组的联系,所以这段代码却能够经过,很让人难以理解。本人在给 Taylor Otwell 提出 PR 时,做者原话为“I'm not making any of these changes to the container on a patch release.”。也许,在之后(5.5或之后)版本做者会更新这里的逻辑,咱们就能够看看服务容器对别名绑定的态度了,你们也最好不要这样用。
服务容器中不少函数都有闭包函数,这些闭包函数能够放入特定的参数,在绑定或者解析过程当中,这些参数会被服务容器自动带入各类类对象或者服务容器实例。
public function testAliasesWithArrayOfParameters() { $container = new Container; $container->bind('foo', function ($app, $config) { return $config; }); $container->alias('foo', 'baz'); $this->assertEquals([1, 2, 3], $container->makeWith('baz', [1, 2, 3])); }
public function testExtendedBindings() { $container = new Container; $container['foo'] = 'foo’; $container->extend('foo', function ($old, $container) { return $old.'bar’; }); $this->assertEquals('foobar', $container->make('foo')); $container = new Container; $container->singleton('foo', function () { return (object) ['name' => 'taylor']; }); $container->extend('foo', function ($old, $container) { $old->age = 26; return $old; }); $result = $container->make('foo'); $this->assertEquals('taylor', $result->name); $this->assertEquals(26, $result->age); $this->assertSame($result, $container->make('foo')); }
public function testCallWithBoundMethod() { $container = new Container; $container->bindMethod('Illuminate\Tests\Container\ContainerTestCallStub@unresolvable', function ($stub,$container) { $container['foo'] = 'foo'; return $stub->unresolvable('foo', 'bar'); }); $result = $container->call('Illuminate\Tests\Container\ContainerTestCallStub@unresolvable'); $this->assertEquals(['foo', 'bar'], $result); $this->assertEquals('foo',$container['foo']); }
public function testResolvingCallbacksAreCalledForSpecificAbstracts() { $container = new Container; $container->resolving('foo', function ($object,$container) { return $object->name = 'taylor'; }); $container->bind('foo', function () { return new StdClass; }); $instance = $container->make('foo'); $this->assertEquals('taylor', $instance->name); }
public function testReboundListeners() { $container = new Container; $container->bind('foo', function () { return 'foo'; }); $container->rebinding('foo', function ($container,$object) { $container['bar'] = $object.'bar'; }); $container->bind('foo', function () { }); $this->assertEquals('bar',$container['foobar']); }