說明 1. 或許是全網首發,我翻過很多文章,從未有一個博主講過這個東西,很多博主只講了IOC、DI和反射機制的常見用法,因類類型形參反射的巧妙用法有相當高的難度和學習盲區,所以從未有人講過類類型的形參它怎麼就被自動實例化的。 2. 在Laravel框架,或者是其它框架中,類的成員方法中形參的類型定義 ...
說明
1. 或許是全網首發,我翻過很多文章,從未有一個博主講過這個東西,很多博主只講了IOC、DI和反射機制的常見用法,因類類型形參反射的巧妙用法有相當高的難度和學習盲區,所以從未有人講過類類型的形參它怎麼就被自動實例化的。
2. 在Laravel框架,或者是其它框架中,類的成員方法中形參的類型定義為某個類,在方法體內就直接當做對象來調用,這並不是PHP本身自帶的語法,而是利用了反射機制,一直很好奇是怎麼實現的。然而框架源碼又太繁重,所以採用原生的方式實現。
3. 反射的功能非常強大,反射可以針對類本身做很多開掛操作,因此PHP框架才會變得這麼易用與強大,類類型形參實例化,僅僅是冰山一角,官方文檔。
代碼
<?php
/**
* @Class 封裝一個可自動實例化類的類類型的成員方法形參的容器
*/
class AutoNew {
/**
* @function 遞歸實例化類構造方法中的數據
* @param $class string 類名
* @return object|null
* @throws ReflectionException
*/
public function newConstructClassTypeParam($class) {
//實例化反射類
$reflection = new ReflectionClass($class);
//獲取類的構造函數
$constructor = $reflection->getConstructor();
if ($constructor === null) {
//如果構造函數存在,實例化這個類,(此方法支持給構造函數傳參,如果有參數的話)
return $reflection->newInstanceArgs();
}
//獲取構造方法的參數,結果返回一個數組,若有值,返回的是ReflectionParameter類
$params = $constructor->getParameters();
$dependencies = [];
foreach ($params as $param) {
//獲取構造方法參數的類型,註意:之有在變數左邊聲明類型的才可以,例如string $p獲取的是string,但$p = 'ab'獲取的就是null
$dependencyType = $param->getType();
//isBuiltin()方法,返回bool值,參數類型修飾符為string/int/bool/float/callable/array/object/mixed的都為true,但傳輸的內置外置介面,類都為false。目前使用PHP8,無法聲明形參為null和resource類型
if (($dependencyType !== null) && (! $dependencyType->isBuiltin())) {
//獲取形參聲明的類型,返回字元串類型,字元串就是字元串,介面就是介面,類就是類
$dependencyClassName = $dependencyType->getName();
//此處可以理解為如果形參是類,或者是含有構造方法的抽象類或者介面的構造方法的形參中有以上類型,就遞歸實例化它
$dependencies[] = $this->newConstructClassTypeParam($dependencyClassName);
} else {
//檢測形參是否有預設值,如果有返回預設值,如果沒有,返回null
$dependencies[] = $param->isOptional() ? $param->getDefaultValue() : null;
}
}
//如果構造函數存在,實例化這個類,(此方法支持給構造函數傳參,如果有參數的話)
return $reflection->newInstanceArgs($dependencies);
}
/**
* @function 自動實例化某個類中某個方法的類類型的形參
* @param $class string 類名
* @param $method string 方法名
* @param $parameters array 參數名
* @return void
* @throws ReflectionException
*/
public function init($class, $method, $parameters = []) {
//實例化PHP內置的反射類
$reflection = new ReflectionClass($class);
//檢查方法是否已定義
if ($reflection->hasMethod($method)) {
//創建一個實例
$instance = $this->newConstructClassTypeParam($class);
//返回一個ReflectionMethod對象,獲取方法的形參,和其它元信息,並填充到ReflectionMethod對象中
$methodReflection = $reflection->getMethod($method);
//返回數組,獲取乾凈的形參數據
$methodParams = $methodReflection->getParameters();
$methodDependencies = [];
foreach ($methodParams as $param) {
//獲取方法參數的類型,註意:只有在變數左邊聲明類型的才可以,例如string $p獲取的是string,但$p = 'ab'獲取的就是null
$paramType = $param->getType();
//isBuiltin()方法,返回bool值,參數類型修飾符為string/int/bool/float/callable/array/object/mixed的都為true,但傳輸的內置外置介面,類都為false。目前使用PHP8,無法將形參聲明null和resource類型
if (($paramType !== null) && (! $paramType->isBuiltin())) {
//獲取形參聲明的類型,返回字元串類型,字元串就是字元串,介面就是介面名,類就是類名
$dependencyClassName = $paramType->getName();
//返回數組,數組的值是創建出來的對象。此處的邏輯可以理解為類成員方法的形參是類的,就實例化它
$methodDependencies[] = $this->newConstructClassTypeParam($dependencyClassName);
} else {
//判斷遍歷出來的形參,在不在實際傳遞的實參數組中,如果在把這個值返回,如果不在,判斷是否有預設值,如果有則返回,如果沒有預設值,賦值為null
if (array_key_exists($param->getName(), $parameters)) {
$args = $parameters[$param->getName()];
} elseif ($param->isOptional()) {
$args = $param->getDefaultValue();
} else {
$args = null;
}
$methodDependencies[] = $args;
}
}
//調用創建的實例並傳參
$methodReflection->invokeArgs($instance, $methodDependencies);
}
}
}
//調用端-------------------------------------------------------------------------------------
//相當於框架業務邏輯層的代碼
class StudentService {
/**
* @function 通過班級id查詢一個班有多少人
* @param $class_id
* @return int
*/
public function getStudentNum($class_id) {
//sql ...
return 123;
}
}
//相當於框架的控制器
class StudentController {
/**
* @function 此方法根據邏輯可要可不要
* @return void
*/
public function __construct() {
echo '構造方法被調用了' . PHP_EOL;
}
/**
* @function 模擬獲取學生人數的方法
* @param $usersService StudentService 學生服務類
* @param $class_id int 班級id
* @param $unit string 單位
* @return void
*/
public function getStudentNum(StudentService $usersService, $class_id, $unit = '人') {
echo $class_id . '班有' . $usersService->getStudentNum($class_id) . $unit . PHP_EOL;
}
}
//初始化Language類並調用getStudentNum方法且傳參,傳參的過程相當與前端請求介面攜帶的參數。整體相當於框架的路由
(new AutoNew())->init(StudentController::class, 'getStudentNum', ['class_id' => '7', 'unit' => '名學生']);
//結果如下:
構造方法被調用了
7班有123名學生