PHP具有完整的反射 API,提供了對類、介面、函式、方法和擴充套件進行逆向工程的能力。通過類的反射提供的能力我們能夠知道類是如何被定義的,它有什麼屬性、什麼方法、方法都有哪些引數,類檔案的路徑是什麼等很重要的資訊。也正式因為類的反射很多PHP框架才能實現依賴注入自動解決類與類之間的依賴關係,這給我們平時的開發帶來了很大的方便。 本文主要是講解如何利用類的反射來實現依賴注入(Dependency Injection),並不會去逐條講述 PHP Reflection 裡的每一個 API ,詳細的 API 參考資訊請查閱官方文件。
為了更好地理解,我們通過一個例子來看類的反射,以及如何實現依賴注入。
下面這個 Car
類代表汽車的資訊。
/**
* Car 汽車資訊
*/
class Car
{
public $gearbox;
public $engine;
/**
* @param $gearbox // 變速箱
* @param $engine // 汽車引擎
*/
public function __construct($gearbox = '4AT', $engine = 'V8')
{
$this->gearbox = $gearbox;
$this->engine = $engine;
}
}
接下里這個 Brand
類代表汽車品牌,它在建構函式中依賴了 Car
汽車資訊類。
/**
* Brand 汽車品牌
*/
class Brand
{
public $car;
public $name;
const WHEEL = 4; // 4個輪子
/**
* @param Car $car // 依賴 Car 類
* @param $name // 品牌名稱
*/
public function __construct(Car $car, $name = '五菱')
{
$this->car = $car;
$this->name = $name;
}
public function carInfo()
{
printf($this->name . '汽車是一輛' . $this->car->gearbox . '變速箱' . $this->car->engine . '發動機的' . self::WHEEL . '輪車。');
}
}
ReflectionClass
下面我們通過反射來對 Brand
這個類進行反向工程。 把 Brand
類的名字傳遞給 ReflectionClass
來例項化一個 ReflectionClass
類的物件。
$reflectionClass = new ReflectionClass(Brand::class);
// 返回值
object(ReflectionClass)#1 (1) {
["name"]=>
string(5) "Brand"
}
反射出類的常量
$reflectionClass->getConstants();
// 返回值
array(1) {
["WHEEL"]=>
int(4)
}
反射出類中定義的方法
$reflectionClass->getMethods();
// 返回值:由 ReflectionMethod 物件構成的陣列
array(2) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(11) "__construct"
["class"]=>
string(5) "Brand"
}
[1]=>
object(ReflectionMethod)#3 (2) {
["name"]=>
string(7) "carInfo"
["class"]=>
string(5) "Brand"
}
}
我們還可以通過 getConstructor()
來單獨獲取類的構造方法,其返回值為一個 ReflectionMethod
物件。
$constructor = $reflection->getConstructor();
反射出某方法的引數
$parameters = $constructor->getParameters();
// 返回值:ReflectionParameter 物件構成的陣列。
array(2) {
[0]=>
object(ReflectionParameter)#3 (1) {
["name"]=>
string(3) "car"
}
[1]=>
object(ReflectionParameter)#4 (1) {
["name"]=>
string(4) "name"
}
}
依賴注入
接下來我們編寫一個名為 make
的方法,傳遞類名稱給 make
以返回類的物件,在 make
裡它會幫我們注入類的依賴,即在本例中幫我們注入 Car
物件給 Brand
類的構造方法。
/**
* 構建類與類的依賴
* @param $className
* @return object|null
* @throws ReflectionException
*/function make($className)
{
$reflectionClass = new ReflectionClass($className);
$constructor = $reflectionClass->getConstructor(); // 獲取建構函式方法
$parameters = $constructor->getParameters(); // 獲取建構函式的引數
$dependencies = getDependencies($parameters); // 開始解析依賴
return $reflectionClass->newInstanceArgs($dependencies); // 返回實體類
}
/**
* 依賴解析 遞迴呼叫
* @param $parameters // 由 ReflectionParameter 物件組成的陣列
* @return array
* @throws ReflectionException
*/
function getDependencies($parameters)
{
$dependencies = [];
foreach($parameters as $parameter) {
/**
* 通過引數名稱,使用 getClass() 方法, 獲取 ReflectionClass 類的物件
* 我們 Brand 的建構函式第一個引數是 Car 類,這裡的程式碼流程就是:
* 第一次迴圈時: $parameter 的引數為 car, $dependency 此時是通過 getClass() 方法獲取到了 Car 的反射類。
* - 因為獲取到了反射類,所以不會走 if(is_null()),而是走到下面的 else 的遞迴呼叫。
* - 再次返回到上面的 make() 方法,此時獲取的反射類是 Car Car Car! $paramters 有兩個引數,這兩個引數分別是 Car 的構造方法中的 $gearbox 和 $engine
* - 當再次進入這個 foreach 迴圈時, $parameter 無法再通過 getClass() 方法獲取反射類,就會走到 if(is_null()) 方法,isDefaultValueAvailable() 方法是用於判斷建構函式的引數是否有預設值,如果有將它加入到 $dependencies 陣列中。如果沒有,我們需要「補0」,用於建構函式必須含有引數的情況。
* - 到此,Car 類就被依賴注入成功了。
* 第二次迴圈時:因為 $dependency 無法通過 $name 的值獲取到反射類,那和上面的 $gearbox 與 $engine 的流程相同。
*/
$dependency = $parameter->getClass();
if (is_null($dependency)) {
if($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
} else {
// 不是可選引數的為了簡單直接賦值為字串0
// 針對構造方法的必須引數這個情況
// Laravel 是通過 service provider 註冊 closure 到 IocContainer,
// 在 closure 裡可以通過 return new Class($param1, $param2) 來返回類的例項
// 然後在 make 時回撥這個 closure 即可解析出物件
// 具體細節我會在另一篇文章裡面描述
$dependencies[] = '0';
}
} else {
//遞迴解析出依賴類的物件
$dependencies[] = make($parameter->getClass()->name);
}
}
return $dependencies;
}
$brand = make(Brand::class);
$brand->carInfo(); // 執行結果:五菱汽車是一輛4AT變速箱V8發動機的4輪車。
到此,一個 Brand
已經被例項化成功了,你能通過上面的程式碼想象出 Brand
例項的資料結構嗎?給你時間思考:
…
5
4
3
2
1
…
$brand
的最終資料結構是:
object(Brand)#5 (2) {
["car"]=>
object(Car)#10 (2) {
["gearbox"]=>
string(3) "4AT"
["engine"]=>
string(2) "V8"
}
["name"]=>
string(6) "五菱"
}
是否和你的想法一致呢?如果一致說明你已經理解類的反射與依賴注入原理了,恭喜你
如果還是不理解,也不要灰心,從頭再看一遍,依賴注入是 Laravel 框架中最核心的部分,理解它對於你後面的學習是非常有幫助的,加油。
本作品採用《CC 協議》,轉載必須註明作者和本文連結