在上一篇文章中咱們講到了 ThinkPHP 如何實現自動加載,若是想看的話能夠看
ThinkPHP5.1 源碼淺析(二)自動加載機制php
在閱讀本篇文章 以前,我但願你掌握了 IOC 、DI 、Facade的基本知識,若是不瞭解,請先查看着幾篇文章。thinkphp
深刻理解控制反轉(IoC)和依賴注入(DI)segmentfault
那麼步入正題。cookie
基於分析框架的 入口腳本文件index.php
閉包
// 加載基礎文件 require __DIR__ . '/../thinkphp/base.php'; // 支持事先使用靜態方法設置Request對象和Config對象 // 執行應用並響應 Container::get('app')->run()->send();
上面 base.php
中的做用是載入自動加載機制,和異常處理,以及開啓日誌功能。app
// 執行應用並響應 Container::get('app')->run()->send();
在這裏纔是使用了 IOC 容器功能,獲取 app 這個容器1框架
進入 Container 中以後咱們先介紹他的類屬性函數
protected static $instance; // 定義咱們的容器類實例,採用單例模式,只實例化一次 protected $instances = []; // 容器中的對象實例 protected $bind = []; // 容器綁定標識 protected $name = []; // 容器標識別名
$instances
是實現了 註冊樹模式2,存儲值測試
array ( 'think\\App' => App實例, 'think\\Env' => Env實例, 'think\\Config' => Config實例, ... )
bind
在初始化時,會載入一堆初始數據,記錄一堆類別名和 類名的映射關係。ui
protected $bind = [ 'app' => 'think\\App', 'build' => 'think\\Build', 'cache' => 'think\\Cache', 'config' => 'think\\Config', 'cookie' => 'think\\Cookie', ... ]
name
和 bind
屬性記錄的值都是很相似的,都是 類別名和 類名的映射關係。區別是,name
記錄的是 已經實例化後的 映射關係。
進入get方法
public static function get($abstract, $vars = [], $newInstance = false) { return static::getInstance()->make($abstract, $vars, $newInstance); }
這一段代碼沒什麼好講的,就是先獲取當前容器的實例(單例),並實例化。
進入 make 方法
public function make($abstract, $vars = [], $newInstance = false) { if (true === $vars) { // 老是建立新的實例化對象 $newInstance = true; $vars = []; } // 若是已經存在而且實例化的類,就用別名拿到他的類 $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; // 若是已經實例化,而且不用每次建立新的實例的話,就直接返回註冊樹上的實例 if (isset($this->instances[$abstract]) && !$newInstance) { return $this->instances[$abstract]; } // 若是咱們綁定過這個類,例如 'app' => 'think\\App', if (isset($this->bind[$abstract])) { $concrete = $this->bind[$abstract]; // 由於ThinkPHP 實現能夠綁定一個閉包或者匿名函數進入,這裏是對閉包的處理 if ($concrete instanceof Closure) { $object = $this->invokeFunction($concrete, $vars); } else { // 記錄 映射關係,並按照 類名來實例化,如 think\\App $this->name[$abstract] = $concrete; return $this->make($concrete, $vars, $newInstance); } } else { // 按照類名調用該類 $object = $this->invokeClass($abstract, $vars); } if (!$newInstance) { $this->instances[$abstract] = $object; } // 返回製做出來的該類 return $object; }
咱們拆分一下,
if (true === $vars) { // 老是建立新的實例化對象 $newInstance = true; $vars = []; }
這段代碼是 讓咱們函數能夠 使用 make($abstract, true)
的方式調用此函數,使咱們每次獲得的都是新的實例。(我以爲這種方式不是很好,每一個變量的形成含義不明確)
// 若是已經存在而且實例化的類,就用別名拿到他的類 $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; // 若是已經實例化,而且不用每次建立新的實例的話,就直接返回註冊樹上的實例 if (isset($this->instances[$abstract]) && !$newInstance) { return $this->instances[$abstract]; }
前面說過,name
中存放的是已經實例化的 別名=> 類名 的映射關係,咱們在這裏嘗試取出 類名,若是該類實例化,就直接返回。
// 若是咱們綁定過這個類,例如 'app' => 'think\\App', if (isset($this->bind[$abstract])) { $concrete = $this->bind[$abstract]; // 由於ThinkPHP 實現能夠綁定一個閉包或者匿名函數進入,這裏是對閉包的處理 if ($concrete instanceof Closure) { $object = $this->invokeFunction($concrete, $vars); } else { // 記錄 映射關係,並按照 類名來實例化,如 think\\App $this->name[$abstract] = $concrete; return $this->make($concrete, $vars, $newInstance); } } else { // 按照類名調用該類 $object = $this->invokeClass($abstract, $vars); }
這裏是看咱們須要容器加載的類是否之前綁定過別名(咱們也能夠直接 bind('classNickName')
來設置一個)
在上面的 IOC 容器中,咱們須要$ioc->get('test');
才能拿到 test 類,才能使用咱們的$user->hello()
方法進行打招呼,有了門面以後,咱們能夠直接 用Test::hello()
進行靜態調用,下面咱們就來介紹一下這個
在咱們編寫代碼時常常會用到 facade
包下的類來接口的靜態調用,咱們在這裏舉一下官網的例子
假如咱們定義了一個app\common\Test
類,裏面有一個hello
動態方法。
<?php namespace app\common; class Test { public function hello($name) { return 'hello,' . $name; } }
調用hello方法的代碼應該相似於:
$test = new \app\common\Test; echo $test->hello('thinkphp'); // 輸出 hello,thinkphp
接下來,咱們給這個類定義一個靜態代理類app\facade\Test
(這個類名不必定要和Test
類一致,但一般爲了便於管理,建議保持名稱統一)。
<?php namespace app\facade; use think\Facade; class Test extends Facade { protected static function getFacadeClass() { return 'app\common\Test'; } }
只要這個類庫繼承think\Facade
,就可使用靜態方式調用動態類app\common\Test
的動態方法,例如上面的代碼就能夠改爲:
// 無需進行實例化 直接以靜態方法方式調用hello echo \app\facade\Test::hello('thinkphp');
結果也會輸出 hello,thinkphp
。
說的直白一點,Facade功能可讓類無需實例化而直接進行靜態方式調用。
使用Facades其實最主要的就是它提供了簡單,易記的語法,從而無需手動注入或配置長長的類名。此外,因爲他們對 PHP 靜態方法的獨特調用,使得測試起來很是容易。