laravel 學習筆記 —— 神奇的服務容器

容器,字面上理解就是裝東西的東西。常見的變量、對象屬性等均可以算是容器。一個容器可以裝什麼,所有取決於你對該容器的定義。固然,有這樣一種容器,它存放的不是文本、數值,而是對象、對象的描述(類、接口)或者是提供對象的回調,經過這種容器,咱們得以實現許多高級的功能,其中最常提到的,就是 「解耦」 、「依賴注入(DI)」。本文就從這裏開始。php

IoC 容器, laravel 的核心

Laravel 的核心就是一個 IoC 容器,根據文檔,稱其爲「服務容器」,顧名思義,該容器提供了整個框架中須要的一系列服務。做爲初學者,不少人會在這一個概念上犯難,所以,我打算從一些基礎的內容開始講解,經過理解面向對象開發中依賴的產生和解決方法,來逐漸揭開「依賴注入」的面紗,逐漸理解這一神奇的設計理念。java

本文一大半內容都是經過舉例來讓讀者去理解什麼是 IoC(控制反轉) 和 DI(依賴注入),經過理解這些概念,來更加深刻。更多關於 laravel 服務容器的用法建議閱讀文檔便可。laravel

 

IoC 容器誕生的故事

講解 IoC 容器有不少的文章,我以前也寫過。但如今我打算利用當下的靈感從新來過,那麼開始吧。數據庫

超人和超能力,依賴的產生!

面向對象編程,有如下幾樣東西無時不刻的接觸:接口還有對象。這其中,接口是類的原型,一個類必需要遵照其實現的接口;對象則是一個類實例化後的產物,咱們稱其爲一個實例。固然這樣說確定不利於理解,咱們就實際的寫點中看不中用的代碼輔助學習。編程

怪物橫行的世界,總歸須要點超級人物來擺平。框架

咱們把一個「超人」做爲一個類,編程語言

  1. class Superman {}

咱們能夠想象,一個超人誕生的時候確定擁有至少一個超能力,這個超能力也能夠抽象爲一個對象,爲這個對象定義一個描述他的類吧。一個超能力確定有多種屬性、(操做)方法,這個盡情的想象,可是目前咱們先大體定義一個只有屬性的「超能力」,至於能幹啥,咱們之後再豐富:ide

  1. class Power {
  2. /**
  3. * 能力值
  4. */
  5. protected $ability;
  6.  
  7. /**
  8. * 能力範圍或距離
  9. */
  10. protected $range;
  11.  
  12. public function __construct($ability, $range)
  13. {
  14. $this->ability = $ability;
  15. $this->range = $range;
  16. }
  17. }

這時候咱們回過頭,修改一下以前的「超人」類,讓一個「超人」建立的時候被賦予一個超能力:函數

  1. class Superman
  2. {
  3. protected $power;
  4.  
  5. public function __construct()
  6. {
  7. $this->power = new Power(999, 100);
  8. }
  9. }

這樣的話,當咱們建立一個「超人」實例的時候,同時也建立了一個「超能力」的實例,可是,咱們看到了一點,「超人」和「超能力」之間不可避免的產生了一個依賴。工具

所謂「依賴」,就是 「我若依賴你,我就不能離開你」。

在一個貫徹面向對象編程的項目中,這樣的依賴隨處可見。少許的依賴並不會有太過直觀的影響,咱們隨着這個例子逐漸鋪開,讓你們慢慢意識到,當依賴達到一個量級時,是怎樣一番噩夢般的體驗。固然,我也會天然而然的講述如何解決問題。

一堆亂麻 —— 可怕的依賴

以前的例子中,超能力類實例化後是一個具體的超能力,可是咱們知道,超人的超能力是多元化的,每種超能力的方法、屬性都有不小的差別,無法經過一種類描述徹底。咱們如今進行修改,咱們假設超人能夠有如下多種超能力:

  • 飛行,屬性有:飛行速度、持續飛行時間
  • 蠻力,屬性有:力量值
  • 能量彈,屬性有:傷害值、射擊距離、同時射擊個數

咱們建立了以下類:

  1. class Flight
  2. {
  3. protected $speed;
  4. protected $holdtime;
  5. public function __construct($speed, $holdtime) {}
  6. }
  7.  
  8. class Force
  9. {
  10. protected $force;
  11. public function __construct($force) {}
  12. }
  13.  
  14. class Shot
  15. {
  16. protected $atk;
  17. protected $range;
  18. protected $limit;
  19. public function __construct($atk, $range, $limit) {}
  20. }

*爲了省事兒我沒有詳細寫出 __construct() 這個構造函數的所有,只寫了須要傳遞的參數。

好了,這下咱們的超人有點「忙」了。在超人初始化的時候,咱們會根據須要來實例化其擁有的超能力嗎,大體以下:

  1. class Superman
  2. {
  3. protected $power;
  4.  
  5. public function __construct()
  6. {
  7. $this->power = new Fight(9, 100);
  8. // $this->power = new Force(45);
  9. // $this->power = new Shot(99, 50, 2);
  10. /*
  11. $this->power = array(
  12. new Force(45),
  13. new Shot(99, 50, 2)
  14. );
  15. */
  16. }
  17. }

咱們須要本身手動的在構造函數內(或者其餘方法裏)實例化一系列須要的類,這樣並很差。能夠想象,假如需求變動(不一樣的怪物橫行地球),須要更多的有針對性的 新的 超能力,或者須要 變動 超能力的方法,咱們必須 從新改造 超人。換句話說就是,改變超能力的同時,我還得從新制造個超人。效率過低了!新超人還沒創造完成世界早已被毀滅。

這時,靈機一動的人想到:爲何不能夠這樣呢?超人的能力能夠被隨時更換,只須要添加或者更新一個芯片或者其餘裝置啥的(想到鋼鐵俠沒)。這樣的話就不要整個從新來過了。

對,就是這樣的。

咱們不該該手動在 「超人」 類中固化了他的 「超能力」 初始化的行爲,而轉由外部負責,由外部創造超能力模組、裝置或者芯片等(咱們後面統一稱爲 「模組」),植入超人體內的某一個接口,這個接口是一個既定的,只要這個 「模組」 知足這個接口的裝置均可以被超人所利用,能夠提高、增長超人的某一種能力。這種由外部負責其依賴需求的行爲,咱們能夠稱其爲 「控制反轉(IoC)」。

工廠模式,依賴轉移!

固然,實現控制反轉的方法有幾種。在這以前,不如咱們先了解一些好玩的東西。

咱們能夠想到,組件、工具(或者超人的模組),是一種可被生產的玩意兒,生產的地方固然是 「工廠(Factory)」,因而有人就提出了這樣一種模式: 工廠模式

工廠模式,顧名思義,就是一個類所依賴的外部事物的實例,均可以被一個或多個 「工廠」 建立的這樣一種開發模式,就是 「工廠模式」。

咱們爲了給超人制造超能力模組,咱們建立了一個工廠,它能夠製造各類各樣的模組,且僅須要經過一個方法:

  1. class SuperModuleFactory
  2. {
  3. public function makeModule($moduleName, $options)
  4. {
  5. switch ($moduleName) {
  6. case 'Fight': return new Fight($options[0], $options[1]);
  7. case 'Force': return new Force($options[0]);
  8. case 'Shot': return new Shot($options[0], $options[1], $options[2]);
  9. }
  10. }
  11. }

這時候,超人 建立之初就可使用這個工廠!

  1. class Superman
  2. {
  3. protected $power;
  4.  
  5. public function __construct()
  6. {
  7. // 初始化工廠
  8. $factory = new SuperModuleFactory;
  9.  
  10. // 經過工廠提供的方法制造須要的模塊
  11. $this->power = $factory->makeModule('Fight', [9, 100]);
  12. // $this->power = $factory->makeModule('Force', [45]);
  13. // $this->power = $factory->makeModule('Shot', [99, 50, 2]);
  14. /*
  15. $this->power = array(
  16. $factory->makeModule('Force', [45]),
  17. $factory->makeModule('Shot', [99, 50, 2])
  18. );
  19. */
  20. }
  21. }

能夠看得出,咱們再也不須要在超人初始化之初,去初始化許多第三方類,只需初始化一個工廠類,便可知足需求。但這樣彷佛和之前區別不大,只是沒有那麼多 new 關鍵字。其實咱們稍微改造一下這個類,你就明白,工廠類的真正意義和價值了。

  1. class Superman
  2. {
  3. protected $power;
  4.  
  5. public function __construct(array $modules)
  6. {
  7. // 初始化工廠
  8. $factory = new SuperModuleFactory;
  9.  
  10. // 經過工廠提供的方法制造須要的模塊
  11. foreach ($modules as $moduleName => $moduleOptions) {
  12. $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
  13. }
  14. }
  15. }
  16.  
  17. // 建立超人
  18. $superman = new Superman([
  19. 'Fight' => [9, 100],
  20. 'Shot' => [99, 50, 2]
  21. ]);

如今修改的結果使人滿意。如今,「超人」 的建立再也不依賴任何一個 「超能力」 的類,咱們如若修改了或者增長了新的超能力,只須要針對修改 SuperModuleFactory 便可。擴充超能力的同時再也不須要從新編輯超人的類文件,使得咱們變得很輕鬆。可是,這纔剛剛開始。

再進一步!IoC 容器的重要組成 —— 依賴注入!

由 「超人」 對 「超能力」 的依賴變成 「超人」 對 「超能力模組工廠」 的依賴後,對付小怪獸們變得更加駕輕就熟。但這也正如你所看到的,依賴並未解除,只是由原來對多個外部的依賴變成了對一個 「工廠」 的依賴。假如工廠出了點麻煩,問題變得就很棘手。

其實大多數狀況下,工廠模式已經足夠了。工廠模式的缺點就是:接口未知(即沒有一個很好的契約模型,關於這個我立刻會有解釋)、產生對象類型單一。總之就是,仍是不夠靈活。雖然如此,工廠模式依舊十分優秀,而且適用於絕大多數狀況。不過咱們爲了講解後面的 依賴注入 ,這裏就先誇大一下工廠模式的缺陷咯。

咱們知道,超人依賴的模組,咱們要求有統一的接口,這樣才能和超人身上的注入接口對接,最終起到提高超能力的效果。

事實上,我以前說謊了,不只僅只有一堆小怪獸,還有更多的大怪獸。嘿嘿。額,這時候彷佛工廠的生產能力顯得有些不足 —— 因爲工廠模式下,全部的模組都已經在工廠類中安排好了,若是有新的、高級的模組加入,咱們必須修改工廠類(比如增長新的生產線):

  1. class SuperModuleFactory
  2. {
  3. public function makeModule($moduleName, $options)
  4. {
  5. switch ($moduleName) {
  6. case 'Fight': return new Fight($options[0], $options[1]);
  7. case 'Force': return new Force($options[0]);
  8. case 'Shot': return new Shot($options[0], $options[1], $options[2]);
  9. // case 'more': .......
  10. // case 'and more': .......
  11. // case 'and more': .......
  12. // case 'oh no! its too many!': .......
  13. }
  14. }
  15. }

看到沒。。。噩夢般的感覺!

其實靈感就差一步!你可能會想到更爲靈活的辦法!對,下一步就是咱們今天的主要配角 —— DI (依賴注入)

因爲對超能力模組的需求不斷增大,咱們須要集合整個世界的高智商人才,一塊兒解決問題,不該該僅僅只有幾個工廠壟斷負責。不太高智商人才們都很是自負,認爲本身的想法是對的,創造出的超能力模組沒有統一的接口,天然而然沒法被正常使用。這時咱們須要提出一種契約,這樣不管是誰創造出的模組,都符合這樣的接口,天然就可被正常使用。

  1. interface SuperModuleInterface
  2. {
  3. /**
  4. * 超能力激活方法
  5. *
  6. * 任何一個超能力都得有該方法,並擁有一個參數
  7. *@param array $target 針對目標,能夠是一個或多個,本身或他人
  8. */
  9. public function activate(array $target);
  10. }

上文中,咱們定下了一個接口 (超能力模組的規範、契約),全部被創造的模組必須遵照該規範,才能被生產。

其實,這就是 php 中 接口( interface ) 的用處和意義!不少人以爲,爲何 php 須要接口這種東西?難道不是 java 、 C# 之類的語言纔有的嗎?這麼說,只要是一個正常的面向對象編程語言(雖然 php 能夠面向過程),都應該具有這一特性。由於一個 對象(object) 自己是由他的模板或者原型 —— 類 (class) ,通過實例化後產生的一個具體事物,而有時候,實現統一種方法且不一樣功能(或特性)的時候,會存在不少的類(class),這時候就須要有一個契約,讓你們編寫出能夠被隨時替換卻不會產生影響的接口。這種由編程語言自己提出的硬性規範,會增長更多優秀的特性。

雖然有些繞,但經過咱們接下來的實例,你們會慢慢領會接口帶來的好處。

這時候,那些提出更好的超能力模組的高智商人才,遵循這個接口,建立了下述(模組)類:

  1. /**
  2. * X-超能量
  3. */
  4. class XPower implements SuperModuleInterface
  5. {
  6. public function activate(array $target)
  7. {
  8. // 這只是個例子。。具體自行腦補
  9. }
  10. }
  11.  
  12. /**
  13. * 終極炸彈 (就這麼俗)
  14. */
  15. class UltraBomb implements SuperModuleInterface
  16. {
  17. public function activate(array $target)
  18. {
  19. // 這只是個例子。。具體自行腦補
  20. }
  21. }

同時,爲了防止有些 「磚家」 自做聰明,或者一些叛徒惡意搗蛋,不遵照契約胡亂製造模組,影響超人,咱們對超人初始化的方法進行改造:

  1. class Superman
  2. {
  3. protected $module;
  4.  
  5. public function __construct(SuperModuleInterface $module)
  6. {
  7. $this->module = $module
  8. }
  9. }

改造完畢!如今,當咱們初始化 「超人」 類的時候,提供的模組實例必須是一個 SuperModuleInterface 接口的實現。不然就會提示錯誤。

正是因爲超人的創造變得容易,一個超人也就不須要太多的超能力,咱們能夠創造多個超人,並分別注入須要的超能力模組便可。這樣的話,雖然一個超人只有一個超能力,但超人更容易變多,咱們也不怕怪獸啦!

如今有人疑惑了,你要講的 依賴注入 呢?

其實,上面講的內容,正是依賴注入。

什麼叫作 依賴注入

本文從開頭到如今提到的一系列依賴,只要不是由內部生產(好比初始化、構造函數 __construct 中經過工廠方法、自行手動 new 的),而是由外部以參數或其餘形式注入的,都屬於 依賴注入(DI) 。是否是豁然開朗?事實上,就是這麼簡單。下面就是一個典型的依賴注入:

  1. // 超能力模組
  2. $superModule = new XPower;
  3.  
  4. // 初始化一個超人,並注入一個超能力模組依賴
  5. $superMan = new Superman($superModule);

關於依賴注入這個本文的主要配角,也就這麼多須要講的。理解了依賴注入,咱們就能夠繼續深刻問題。慢慢走近今天的主角……

更爲先進的工廠 —— IoC 容器!

剛剛列了一段代碼:

  1. $superModule = new XPower;
  2.  
  3. $superMan = new Superman($superModule);

讀者應該看出來了,手動的建立了一個超能力模組、手動的建立超人並注入了剛剛建立超能力模組。呵呵,手動。

現代社會,應該是高效率的生產,乾淨的車間,完美的自動化裝配。

一羣怪獸來了,如此低效率產出超人是不現實,咱們須要自動化 —— 最多一條指令,千軍萬馬來相見。咱們須要一種高級的生產車間,咱們只須要向生產車間提交一個腳本,工廠便可以經過指令自動化生產。這種更爲高級的工廠,就是工廠模式的昇華 —— IoC 容器

  1. class Container
  2. {
  3. protected $binds;
  4.  
  5. protected $instances;
  6.  
  7. public function bind($abstract, $concrete)
  8. {
  9. if ($concrete instanceof Closure) {
  10. $this->binds[$abstract] = $concrete;
  11. } else {
  12. $this->instances[$abstract] = $concrete;
  13. }
  14. }
  15.  
  16. public function make($abstract, $parameters = [])
  17. {
  18. if (isset($this->instances[$abstract])) {
  19. return $this->instances[$abstract];
  20. }
  21.  
  22. array_unshift($parameters, $this);
  23.  
  24. return call_user_func_array($this->binds[$abstract], $parameters);
  25. }
  26. }

這時候,一個十分粗糙的容器就誕生了。如今的確很簡陋,但不妨礙咱們進一步提高他。先着眼如今,看看這個容器如何使用吧!

  1. // 建立一個容器(後面稱做超級工廠)
  2. $container = new Container;
  3.  
  4. // 向該 超級工廠 添加 超人 的生產腳本
  5. $container->bind('superman', function($container, $moduleName) {
  6. return new Superman($container->make($moduleName));
  7. });
  8.  
  9. // 向該 超級工廠 添加 超能力模組 的生產腳本
  10. $container->bind('xpower', function($container) {
  11. return new XPower;
  12. });
  13.  
  14. // 同上
  15. $container->bind('ultrabomb', function($container) {
  16. return new UltraBomb;
  17. });
  18.  
  19. // ****************** 華麗麗的分割線 **********************
  20. // 開始啓動生產
  21. $superman_1 = $container->make('superman', ['xpower']);
  22. $superman_2 = $container->make('superman', ['ultrabomb']);
  23. $superman_3 = $container->make('superman', ['xpower']);
  24. // ...隨意添加

看到沒?經過最初的 綁定(bind) 操做,咱們向 超級工廠 註冊了一些生產腳本,這些生產腳本在生產指令下達之時便會執行。發現沒有?咱們完全的解除了 超人 與 超能力模組 的依賴關係,更重要的是,容器類也絲毫沒有和他們產生任何依賴!咱們經過註冊、綁定的方式向容器中添加一段能夠被執行的回調(能夠是匿名函數、非匿名函數、類的方法)做爲生產一個類的實例的 腳本 ,只有在真正的 生產(make) 操做被調用執行時,纔會觸發。

這樣一種方式,使得咱們更容易在建立一個實例的同時解決其依賴關係,而且更加靈活。當有新的需求,只需另外綁定一個「生產腳本」便可。

實際上,真正的 IoC 容器更爲高級。咱們如今的例子中,仍是須要手動提供超人所須要的模組參數,但真正的 IoC 容器會根據類的依賴需求,自動在註冊、綁定的一堆實例中搜尋符合的依賴需求,並自動注入到構造函數參數中去。Laravel 框架的服務容器正是這麼作的。實現這種功能其實理論上並不麻煩,但我並不會在本文中寫出,由於……我懶得寫。

不過我告訴你們,這種自動搜尋依賴需求的功能,是經過 反射(Reflection) 實現的,剛好的,php 完美的支持反射機制!關於反射,php 官方文檔有詳細的資料,而且中文翻譯基本覆蓋,足夠學習和研究!

http://php.net/manual/zh/book.reflection.php

如今,到目前爲止,咱們已經再也不害怕怪獸們了。高智商人才集思廣益,層次分明,根據接口契約創造規範的超能力模組。超人開始批量產出。最終,人人都是超人,你也能夠是哦 stuck_out_tongue_closed_eyes

迴歸正常世界。咱們開始從新審視 laravel 的核心。

如今,咱們開始慢慢解讀 laravel 的核心。其實,laravel 的核心就是一個 IoC 容器,也剛好是我以前所說的高級的 IoC 容器。

能夠說,laravel 的核心自己十分輕量,並無什麼很神奇很實質性的應用功能。不少人用到的各類功能模塊好比 Route(路由)Eloquent ORM(數據庫 ORM 組件)Request and Response(請求和響應)等等等等,實際上都是與核心無關的類模塊提供的,這些類從註冊到實例化,最終被你所使用,其實都是 laravel 的服務容器負責的。

咱們以你們最多見的 Route 類做爲例子。你們可能常常見到路由定義是這樣的:

  1. Route::get('/', function() {
  2. // bla bla bla...
  3. });

實際上, Route 類被定義在這個命名空間:Illuminate\Routing\Router,文件 vendor/laravel/framework/src/Illuminate/Routing/Router.php

咱們經過打開發現,這個類的這一系列方法,如 getpostany 等都不是靜態(static)方法,這是怎麼一回事兒?不要急,咱們繼續。

服務提供者

咱們在前文介紹 IoC 容器的部分中,提到了,一個類須要綁定、註冊至容器中,才能被「製造」。

對,一個類要被容器所可以提取,必需要先註冊至這個容器。既然 laravel 稱這個容器叫作服務容器,那麼咱們須要某個服務,就得先註冊、綁定這個服務到容器,那麼提供服務並綁定服務至容器的東西,就是 服務提供者(ServiceProvider)

雖然,綁定一個類到容器不必定非要經過 服務提供者(ServiceProvider) 。

可是,咱們知道,有時候咱們的類、模塊會有須要其餘類和組件的狀況,爲了保證初始化階段不會出現所須要的模塊和組件沒有註冊的狀況,laravel 將註冊和初始化行爲進行拆分,註冊的時候就只能註冊,初始化的時候就是初始化。拆分後的產物就是如今的 服務提供者

服務提供者主要分爲兩個部分,register(註冊) 和 boot(引導、初始化),具體參考文檔。register 負責進行向容器註冊「腳本」,但要注意註冊部分不要有對未知事物的依賴,若是有,就要移步至 boot 部分。

Facade

咱們如今解答以前關於 Route 的方法爲什麼能以靜態方法訪問的問題。實際上這個問題文檔上有寫,簡單說來就是模擬一個類,提供一個靜態魔術方法__callStatic,並將該靜態方法映射到真正的方法上。

咱們使用的 Route 類其實是 Illuminate\Support\Facades\Route 經過 class_alias() 函數創造的 別名 而已,這個類被定義在文件 vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php 。

咱們打開文件一看……誒?怎麼只有這麼簡單的一段代碼呢?

  1. <?php namespace Illuminate\Support\Facades;
  2.  
  3. /**
  4. * @see \Illuminate\Routing\Router
  5. */
  6. class Route extends Facade {
  7.  
  8. /**
  9. * Get the registered name of the component.
  10. *
  11. * @return string
  12. */
  13. protected static function getFacadeAccessor()
  14. {
  15. return 'router';
  16. }
  17.  
  18. }

其實仔細看,會發現這個類繼承了一個叫作 Facade 的類,到這裏謎底差很少要解開了。

上述簡單的定義中,咱們看到了 getFacadeAccessor 方法返回了一個 route,這是什麼意思呢?事實上,這個值被一個 ServiceProvider 註冊過,你們應該知道註冊了個什麼,固然是那個真正的路由類!

有人會問,Facade 是怎麼實現的。我並不想說得太細,一個是我懶,另外一個緣由就是,本身發現一些東西更容易理解,並不容易忘記。不少細節我已經說了,建議你們自行去研究。

至此,咱們已經講的差很少了。

和平!咱們該總結總結了!

不管如何,世界和平了。

這裏要總結的內容就是,其實不少事情並不複雜,怕的是複雜的理論內容。我以爲不少東西一旦想通也就那麼回事兒。不少人以爲 laravel 這很差那很差、這裏難哪裏難,我只能說,laravel 的確不是一流和優秀的框架,說 laravel 是一流、優秀的框架的人,不是 laravel 的粉絲那麼就是跟風炒做。Laravel 最大的特色和優秀之處就是使用了不少 php 比較新(實際上並不新)的概念和技術(也就一堆語法糖)而已。所以 laravel 的確符合一個適宜學習的框架。Laravel 的構思的確和其餘框架有很大不一樣,這也要求學習他的人必須熟練 php,並 基礎紮實!若是你以爲學 laravel 框架十分困難,那麼緣由只有一個:你 php 基礎很差。

另外,善於利用命名空間和麪向對象的諸多特性,去追尋一些東西,你會發現,原來這一切這麼容易。

https://www.insp.top/learn-laravel-container(原貼地址)

相關文章
相關標籤/搜索