摘要:前言首先歡迎關注我的博客在前面幾個博客中,我詳細講了容器各個功能的使用綁定的源碼解析的源碼,今天這篇博客會詳細介紹容器的一些細節,一些特性,以便更好地掌握容器的功能。
前言
首先歡迎關注我的博客: www.leoyang90.cn
在前面幾個博客中,我詳細講了 Ioc 容器各個功能的使用、綁定的源碼、解析的源碼,今天這篇博客會詳細介紹 Ioc 容器的一些細節,一些特性,以便更好地掌握容器的功能。
注:本文使用的測試類與測試對象都取自 laravel 的單元測試文件src/illuminate/tests/Container/ContainerTest.php
rebind綁定特性 rebind 在綁定之前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 的回調函數是不會調用的:
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 綁定:
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 回調函數在下一次的綁定中被激活,在 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 特性 resolving 回調的類型
resolving 不僅可以針對接口執行回調函數,還可以針對接口實現的類型進行回調函數。
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); }resolving 回調與 instance
前面講過,對于 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 擴展特性
extend 用于擴展綁定對象的功能,對于普通綁定來說,這個函數的位置很靈活:
在綁定前擴展public function testExtendIsLazyInitialized() { ContainerLazyExtendStub::$initialized = false; $container = new Container; $container->extend("IlluminateTestsContainerContainerLazyExtendStub", function ($obj, $container) { $obj->init(); return $obj; }); $container->bind("IlluminateTestsContainerContainerLazyExtendStub"); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->make("IlluminateTestsContainerContainerLazyExtendStub"); $this->assertTrue(ContainerLazyExtendStub::$initialized); }在綁定后解析前擴展
public function testExtendIsLazyInitialized() { ContainerLazyExtendStub::$initialized = false; $container = new Container; $container->bind("IlluminateTestsContainerContainerLazyExtendStub"); $container->extend("IlluminateTestsContainerContainerLazyExtendStub", function ($obj, $container) { $obj->init(); return $obj; }); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->make("IlluminateTestsContainerContainerLazyExtendStub"); $this->assertTrue(ContainerLazyExtendStub::$initialized); }在解析后擴展
public function testExtendIsLazyInitialized() { ContainerLazyExtendStub::$initialized = false; $container = new Container; $container->bind("IlluminateTestsContainerContainerLazyExtendStub"); $container->make("IlluminateTestsContainerContainerLazyExtendStub"); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->extend("IlluminateTestsContainerContainerLazyExtendStub", function ($obj, $container) { $obj->init(); return $obj; }); $this->assertFalse(ContainerLazyExtendStub::$initialized); $container->make("IlluminateTestsContainerContainerLazyExtendStub"); $this->assertTrue(ContainerLazyExtendStub::$initialized); }
可以看出,無論在哪個位置,extend 擴展都有 lazy 初始化的特點,也就是使用 extend 函數并不會立即起作用,而是要等到 make 解析才會激活。
extend 與 instance 綁定對于 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); }extend 綁定與 rebind 回調
無論擴展對象是 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 綁定特性 contextual 在綁定前
contextual 綁定不僅可以與 bind 綁定合作,相互不干擾,還可以與 instance 綁定相互合作。而且 instance 的位置也很靈活,可以在 contextual 綁定前,也可以在contextual 綁定后:
public function testContextualBindingWorksForExistingInstancedBindings() { $container = new Container; $container->instance("IlluminateTestsContainerIContainerContractStub", new ContainerImplementationStub); $container->when("IlluminateTestsContainerContainerTestContextInjectOne")->needs("IlluminateTestsContainerIContainerContractStub")->give("IlluminateTestsContainerContainerImplementationStubTwo"); $this->assertInstanceOf( "IlluminateTestsContainerContainerImplementationStubTwo", $container->make("IlluminateTestsContainerContainerTestContextInjectOne")->impl ); }contextual 在綁定后
public function testContextualBindingWorksForNewlyInstancedBindings() { $container = new Container; $container->when("IlluminateTestsContainerContainerTestContextInjectOne")->needs("IlluminateTestsContainerIContainerContractStub")->give("IlluminateTestsContainerContainerImplementationStubTwo"); $container->instance("IlluminateTestsContainerIContainerContractStub", new ContainerImplementationStub); $this->assertInstanceOf( "IlluminateTestsContainerContainerImplementationStubTwo", $container->make("IlluminateTestsContainerContainerTestContextInjectOne")->impl ); }contextual 綁定與別名
contextual 綁定也可以在別名上進行,無論賦予別名的位置是 contextual 的前面還是后面:
public function testContextualBindingDoesntOverrideNonContextualResolution() { $container = new Container; $container->instance("stub", new ContainerImplementationStub); $container->alias("stub", "IlluminateTestsContainerIContainerContractStub"); $container->when("IlluminateTestsContainerContainerTestContextInjectTwo")->needs("IlluminateTestsContainerIContainerContractStub")->give("IlluminateTestsContainerContainerImplementationStubTwo"); $this->assertInstanceOf( "IlluminateTestsContainerContainerImplementationStubTwo", $container->make("IlluminateTestsContainerContainerTestContextInjectTwo")->impl ); $this->assertInstanceOf( "IlluminateTestsContainerContainerImplementationStub", $container->make("IlluminateTestsContainerContainerTestContextInjectOne")->impl ); } public function testContextualBindingWorksOnNewAliasedBindings() { $container = new Container; $container->when("IlluminateTestsContainerContainerTestContextInjectOne")->needs("IlluminateTestsContainerIContainerContractStub")->give("IlluminateTestsContainerContainerImplementationStubTwo"); $container->bind("stub", ContainerImplementationStub::class); $container->alias("stub", "IlluminateTestsContainerIContainerContractStub"); $this->assertInstanceOf( "IlluminateTestsContainerContainerImplementationStubTwo", $container->make("IlluminateTestsContainerContainerTestContextInjectOne")->impl ); }爭議
目前比較有爭議的是下面的情況:
public function testContextualBindingWorksOnExistingAliasedInstances() { $container = new Container; $container->alias("IlluminateTestsContainerIContainerContractStub", "stub"); $container->instance("stub", new ContainerImplementationStub); $container->when("IlluminateTestsContainerContainerTestContextInjectOne")->needs("stub")->give("IlluminateTestsContainerContainerImplementationStubTwo"); $this->assertInstanceOf( "IlluminateTestsContainerContainerImplementationStubTwo", $container->make("IlluminateTestsContainerContainerTestContextInjectOne")->impl ); }
由于instance的特性,當別名被綁定到其他對象上時,別名 stub 已經失去了與 IlluminateTestsContainerIContainerContractStub 之間的關系,因此不能使用 stub 代替作上下文綁定。
但是另一方面:
public function testContextualBindingWorksOnBoundAlias() { $container = new Container; $container->alias("IlluminateTestsContainerIContainerContractStub", "stub"); $container->bind("stub", ContainerImplementationStub::class); $container->when("IlluminateTestsContainerContainerTestContextInjectOne")->needs("stub")->give("IlluminateTestsContainerContainerImplementationStubTwo"); $this->assertInstanceOf( "IlluminateTestsContainerContainerImplementationStubTwo", $container->make("IlluminateTestsContainerContainerTestContextInjectOne")->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或以后)版本作者會更新這里的邏輯,我們就可以看看服務容器對別名綁定的態度了,大家也最好不要這樣用。
服務容器中的閉包函數參數服務容器中很多函數都有閉包函數,這些閉包函數可以放入特定的參數,在綁定或者解析過程中,這些參數會被服務容器自動帶入各種類對象或者服務容器實例。
bind 閉包參數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])); }extend 閉包參數
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")); }bindmethod 閉包參數
public function testCallWithBoundMethod() { $container = new Container; $container->bindMethod("IlluminateTestsContainerContainerTestCallStub@unresolvable", function ($stub,$container) { $container["foo"] = "foo"; return $stub->unresolvable("foo", "bar"); }); $result = $container->call("IlluminateTestsContainerContainerTestCallStub@unresolvable"); $this->assertEquals(["foo", "bar"], $result); $this->assertEquals("foo",$container["foo"]); }resolve 閉包參數
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); }rebinding 閉包參數
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"]); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/23012.html
摘要:劃下重點,服務容器是用于管理類的依賴和執行依賴注入的工具。類的實例化及其依賴的注入,完全由服務容器自動的去完成。 本文首發于 深入剖析 Laravel 服務容器,轉載請注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。 之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請求,又是如何生成響應并最終呈現給用戶的工作原理。 本章將帶領大...
摘要:哲學的一個重要組成部分就是容器,也可以稱為服務容器。那我們要怎么做呢請看下面的例子數據庫連接通過上面的代碼,如果我們想把改成,根本不需要去修改類構造函數里的依賴。現在我要講下容器里到底發生了什么。 showImg(https://segmentfault.com/img/remote/1460000018868909); IOC 容器是一個實現依賴注入的便利機制 - Taylor?Ot...
摘要:前言年底了不太忙,最近一段時間也一直在研究,就想寫篇關于比較深一點的教程系列啥的,于是就找到站長給開了寫教程的渠道。優點的就是為藝術家創造的框架,它也是工程化的趨勢。項目維護方便也是事實。如果有遇到問題可以直接在教程下面留言。 前言 年底了不太忙,最近一段時間也一直在研究laravel,就想寫篇關于laravel比較深一點的教程系列啥的,于是就找到站長給開了寫教程的渠道。由于第一次寫,...
摘要:如何實現持久化持久化,將在內存中的的狀態保存到硬盤中,相當于備份數據庫狀態。相當于備份數據庫接收到的命令,所有被寫入的命令都是以的協議格式來保存的。 最近社區里面有一篇文章引起了最多程序猿的關注,Laravel、PHPer 面試可能會遇到的問題,看評論區不少小伙伴們被難倒,對于一些問題同樣難倒了我(其實有很多啦),趁著周末有空,又總結梳理了一遍,順便來答一波題。由于個人技術水平有限,答...
閱讀 1293·2021-11-16 11:44
閱讀 3759·2021-10-09 10:01
閱讀 1745·2021-09-24 10:31
閱讀 3833·2021-09-04 16:41
閱讀 2510·2021-08-09 13:45
閱讀 1210·2019-08-30 14:08
閱讀 1776·2019-08-29 18:32
閱讀 1640·2019-08-26 12:12