首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP ReflectionClass 反射類深度解析:動態實例化、方法調用與框架級應用

     編輯:PHP 相關知識

什麼是反射?

ReflectionClass 允許在運行時檢查、操作類的元數據:屬性、方法、常量、接口、父類、註解等。它是框架實現依賴注入、路由分發、插件系統、ORM 映射的核心基礎。

基礎操作

<?php
class Foo {
    public $name = 'Foo';
    private $secret = 'hidden';
    const VERSION = '1.0';

    public function bar($arg) {
        return "bar called with $arg";
    }

    protected function internal() {
        return 'internal';
    }
}

$ref = new ReflectionClass('Foo');

// 1. 獲取類基本信息
echo "類名: " . $ref->getName() . "\n";
echo "文件: " . $ref->getFileName() . "\n";
echo "起始行: " . $ref->getStartLine() . "\n";
echo "結束行: " . $ref->getEndLine() . "\n";
echo "是否抽象: " . ($ref->isAbstract() ? 'yes' : 'no') . "\n";
echo "是否接口: " . ($ref->isInterface() ? 'yes' : 'no') . "\n";
echo "是否可實例化: " . ($ref->isInstantiable() ? 'yes' : 'no') . "\n";

// 2. 獲取常量
print_r($ref->getConstants());

// 3. 獲取屬性 (含訪問控制)
foreach ($ref->getProperties() as $prop) {
    echo "屬性: {$prop->getName()}  訪問: " . implode(', ', Reflection::getModifierNames($prop->getModifiers())) . "\n";
    $prop->setAccessible(true); // 突破 private/protected
    echo "  值: " . var_export($prop->getValue(new Foo()), true) . "\n";
}

// 4. 獲取方法
foreach ($ref->getMethods() as $method) {
    echo "方法: {$method->getName()}  參數數: {$method->getNumberOfParameters()}\n";
}
?>

動態實例化與方法調用

<?php
$ref = new ReflectionClass('Foo');

// 方式 1:newInstance() 等同 new Foo()
$obj1 = $ref->newInstance();

// 方式 2:newInstanceArgs() 支持構造函數參數
class Bar {
    public function __construct(public string $name, public int $age) {}
}
$refBar = new ReflectionClass('Bar');
$obj2 = $refBar->newInstanceArgs(['Tom', 25]);

// 方式 3:無構造函數實例化 (不調用 __construct)
$obj3 = $ref->newInstanceWithoutConstructor();

// 獲取方法對象並調用
$method = $ref->getMethod('bar');
$method->setAccessible(true); // 若為 private/protected
$result = $method->invoke($obj1, 'test argument'); // 等同 $obj1->bar('test argument')
echo $result; // bar called with test argument

// 靜態方法調用
$staticMethod = $ref->getMethod('staticMethod');
$staticMethod->invoke(null, 'arg'); // 第一個參數為 null
?>

框架級實戰:控制器自動分發

模擬 MVC 框架路由分發核心邏輯:

<?php
interface IController {
    public function execute();
}

class UserController implements IController {
    public function execute() { echo "UserController::execute\n"; }
    public function profile() { echo "UserController::profile\n"; }
}

class Dispatcher {
    public function dispatch(string $controllerName, string $action = 'execute') {
        try {
            // 1. 類是否存在
            if (!class_exists($controllerName)) {
                throw new Exception("Controller '$controllerName' not found");
            }

            // 2. 構建反射類
            $rc = new ReflectionClass($controllerName);

            // 3. 驗證實現了接口
            if (!$rc->implementsInterface('IController')) {
                throw new Exception("Controller must implement IController");
            }

            // 4. 驗證方法存在
            if (!$rc->hasMethod($action)) {
                throw new Exception("Action '$action' not found");
            }

            // 5. 實例化控制器
            $controller = $rc->newInstance();

            // 6. 獲取方法並調用
            $method = $rc->getMethod($action);
            $method->invoke($controller);

        } catch (Exception $e) {
            // 統一異常處理:404、500、日誌記錄
            echo "Dispatch Error: " . $e->getMessage();
        }
    }
}

// 使用
$dispatcher = new Dispatcher();
$dispatcher->dispatch('UserController', 'profile'); // 輸出: UserController::profile
?>

進階用法

讀取類註解

<?php
/**
 * @Route("/api/user")
 * @Auth(role="admin")
 */
class ApiController {}

$ref = new ReflectionClass('ApiController');
$docComment = $ref->getDocComment();
// 解析 @Route、@Auth 等註解實現路由註冊、權限檢查
?>

依賴注入容器簡易實現

<?php
class Container {
    private $bindings = [];

    public function bind(string $abstract, $concrete) {
        $this->bindings[$abstract] = $concrete;
    }

    public function make(string $abstract) {
        if (!isset($this->bindings[$abstract])) {
            throw new Exception("No binding for $abstract");
        }
        $concrete = $this->bindings[$abstract];
        $ref = new ReflectionClass($concrete);
        $constructor = $ref->getConstructor();
        if (!$constructor) return $ref->newInstance();

        $params = $constructor->getParameters();
        $args = [];
        foreach ($params as $param) {
            $type = $param->getType();
            if ($type && !$type->isBuiltin()) {
                $args[] = $this->make($type->getName()); // 遞歸解析依賴
            } elseif ($param->isDefaultValueAvailable()) {
                $args[] = $param->getDefaultValue();
            } else {
                throw new Exception("Cannot resolve parameter {$param->getName()}");
            }
        }
        return $ref->newInstanceArgs($args);
    }
}

// 使用
$container = new Container();
$container->bind('db', 'PDO');
$container->bind('UserRepository', 'MySQLUserRepository'); // 假設構造函數需 PDO
$repo = $container->make('UserRepository');
?>

性能與最佳實踐

場景建議
高頻調用 (如每請求路由分發)緩存 ReflectionClass 對象 (APCu/靜態變量),避免重複反射解析
大量實例化優先用 new Class(),僅在動態參數時用 newInstanceArgs()
訪問私有成員謹慎使用 setAccessible(true),破壞封裝,僅用於測試/框架內部
獲取方法參數類型PHP 7.0+ 支持 $param->getType(),結合 ReflectionNamedType 完整解析

常見陷阱

  • ReflectionClass::newInstance() 無法實例化帶有必填構造函數參數的類 → 用 newInstanceArgs()
  • getMethods() 返回繼承鏈所有方法,含父類方法 → 用 $method->getDeclaringClass()->getName() === $ref->getName() 過濾當前類方法
  • 反射內部類 (如 new ReflectionClass('Outer\\Inner')) 需使用完整限定名
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved