在上一篇文章中我們講到了 ThinkPHP 如何實現自動加載,如果想看的話可以看
ThinkPHP5.1 源碼淺析(二)自動加載機制
在閱讀本篇文章 之前,我希望你掌握了 IOC 、DI 、Facade的基本知識,如果不瞭解,請先查看着幾篇文章。
那麼步入正題。
服務調用
基於分析框架的 入口腳本文件index.php
// 加載基礎文件
require __DIR__ . '/../thinkphp/base.php';
// 支持事先使用靜態方法設置Request對象和Config對象
// 執行應用並響應
Container::get('app')->run()->send();
上面 base.php
中的作用是載入自動加載機制,和異常處理,以及開啓日誌功能。
// 執行應用並響應
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
在初始化時,會載入一堆初始數據,記錄一堆類別名和 類名的映射關係。
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')
來設置一個)
- 如果綁定過,那麼就來實例化它。
- 如果沒有,那麼就認定他是一個類名,直接調用。3
門面模式
在上面的 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功能可以讓類無需實例化而直接進行靜態方式調用。
Facade工作原理
- Facede 核心實現原理就是在 Facade 提前注入IoC容器。
- 定義一個服務提供者的外觀類,在該類定義一個類的變量,跟ioc容器綁定的key一樣,
- 通過靜態魔術方法__callStatic可以得到當前想要調用的 hello 方法
- 使用static::$ioc->make('Test');
爲什麼要使用 Facade
使用Facades其實最主要的就是它提供了簡單,易記的語法,從而無需手動注入或配置長長的類名。此外,由於他們對 PHP 靜態方法的獨特調用,使得測試起來非常容易。