反射 是 PHP 元編程的核心,允許運行時檢查類、介面、Trait、方法、屬性、參數、註解等元數據。本文從核心 API 入手,實戰演示「獲取定義位置」、「依賴注入容器雛形」、「屬性/方法可視性控制」、「PHP 8 註解解析」等高頻場景。
| 類別 | 用途 | 常用方法 |
ReflectionClass | 類/介面/Trait/Enum | getName(), getFileName(), getStartLine(), getMethods(), getProperties(), getConstructor(), newInstanceArgs(), getAttributes() |
ReflectionMethod | 方法 | getName(), getParameters(), getReturnType(), invoke(), invokeArgs(), setAccessible() |
ReflectionProperty | 屬性 | getName(), getType(), getValue(), setValue(), setAccessible() |
ReflectionFunction | 函數/閉包 | getName(), getFileName(), getStartLine(), getParameters(), invoke() |
ReflectionParameter | 參數 | getName(), getType(), isOptional(), getDefaultValue(), getAttributes() |
ReflectionAttribute | PHP 8 註解 | getName(), newInstance(), getArguments() |
場景一:獲取類/函數定義位置 (除錯/文檔生成/IDE 跳轉)
<?php
function getDefinitionPosition(string $name): array {
$info = [];
if (class_exists($name)) {
$rc = new ReflectionClass($name);
$info['class_' . $name] = [
'file' => $rc->getFileName(),
'line' => $rc->getStartLine(),
'end_line' => $rc->getEndLine(),
'namespace' => $rc->getNamespaceName(),
];
}
if (function_exists($name)) {
$rf = new ReflectionFunction($name);
$info['function_' . $name] = [
'file' => $rf->getFileName(),
'line' => $rf->getStartLine(),
'end_line' => $rf->getEndLine(),
];
}
return $info;
}
// 測試
class DemoClass { public function test() {} }
function demoFunc() {}
print_r(getDefinitionPosition('DemoClass'));
print_r(getDefinitionPosition('demoFunc'));
/* 輸出範例:
Array
(
[class_DemoClass] => Array
(
[file] => /var/www/html/test.php
[line] => 10
[end_line] => 15
[namespace] =>
)
[function_demoFunc] => Array
(
[file] => /var/www/html/test.php
[line] => 18
[end_line] => 20
)
)
*/
?>
場景二:簡易依賴注入容器 (自動解析建構子參數)
<?php
class Container {
private array $bindings = []; // interface => concrete
private array $instances = []; // singleton cache
public function bind(string $abstract, string $concrete): void {
$this->bindings[$abstract] = $concrete;
}
public function singleton(string $abstract, string $concrete): void {
$this->bind($abstract, $concrete);
}
public function make(string $abstract): mixed {
// 1. 檢查單例快取
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// 2. 解析綁定
$concrete = $this->bindings[$abstract] ?? $abstract;
// 3. 反射建構子參數
$refClass = new ReflectionClass($concrete);
$constructor = $refClass->getConstructor();
if ($constructor === null) {
$instance = $refClass->newInstance();
} else {
$params = $constructor->getParameters();
$args = [];
foreach ($params as $param) {
$type = $param->getType();
if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
// 遞遞解析依賴
$args[] = $this->make($type->getName());
} elseif ($param->isOptional()) {
$args[] = $param->getDefaultValue();
} else {
throw new Exception("Cannot resolve parameter {$param->getName()} for {$concrete}");
}
}
$instance = $refClass->newInstanceArgs($args);
}
// 4. 單例快取
if (isset($this->bindings[$abstract])) {
$this->instances[$abstract] = $instance;
}
return $instance;
}
}
// 使用範例
interface LoggerInterface { public function log(string $msg); }
class FileLogger implements LoggerInterface {
public function __construct(private string $path) {}
public function log(string $msg) { file_put_contents($this->path, $msg . "\n", FILE_APPEND); }
}
class UserService {
public function __construct(private LoggerInterface $logger) {}
public function register(string $name) { $this->logger->log("User registered: $name"); }
}
$container = new Container();
$container->bind(LoggerInterface::class, FileLogger::class);
$container->singleton(FileLogger::class, FileLogger::class); // 綁定實現類自身為單例
// 手動綁定建構子參數 (或用配置檔)
$container->bind('path', '/var/log/app.log'); // 簡化:這裡需擴展容器支援原始值綁定
$userService = $container->make(UserService::class);
$userService->register('Alice');
?>
💡 現代專案請直接使用 PHP-DI、Symfony DependencyInjection、Illuminate Container (Laravel 核心),功能完備、效能優化、支援標籤/自動裝配/代理。
場景三:運行時修改私有屬性/調用私有方法 (測試/遷移/第三方庫擴展)
<?php
class User {
private string $name = 'Guest';
private int $id = 0;
private function internalInit(): void {
$this->id = random_int(1000, 9999);
}
}
$user = new User();
// 正常無法訪問
// $user->name = 'Bob'; // Fatal error
// 反射突破封裝
$ref = new ReflectionClass($user);
$nameProp = $ref->getProperty('name');
$nameProp->setAccessible(true);
$nameProp->setValue($user, 'Bob');
$idProp = $ref->getProperty('id');
$idProp->setAccessible(true);
echo "Original ID: " . $idProp->getValue($user) . "\n"; // 0
// 調用私有方法
$method = $ref->getMethod('internalInit');
$method->setAccessible(true);
$method->invoke($user);
echo "New ID: " . $idProp->getValue($user) . "\n"; // 隨機數
echo "Name: " . $nameProp->getValue($user) . "\n"; // Bob
?>
⚠️ 慎用:破壞封裝導致代碼脆弱、難重構、測試失真。僅用於:單元測試構建特定狀態、遷移舊代碼無法修改源碼時、框架內部實現 (如 ORM 填充私有主鍵)。
場景四:PHP 8 註解屬性 解析 (路由、驗證、ORM 映射)
<?php
#[\Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
class Route {
public function __construct(
public string $path,
public array $methods = ['GET'],
public string $name = ''
) {}
}
#[\Attribute(Attribute::TARGET_PROPERTY)]
class Column {
public function __construct(
public string $name = '',
public string $type = 'string',
public bool $nullable = false
) {}
}
#[Route('/api/users', methods: ['GET', 'POST'], name: 'users.index')]
class UserController {
#[Column(name: 'user_id', type: 'integer')]
public int $id;
#[Column(name: 'user_name', type: 'string', nullable: false)]
public string $name;
#[Route('/api/users/{id}', methods: ['GET'], name: 'users.show')]
public function show(int $id) { /* ... */ }
}
// 解析器
function parseControllerAttributes(string $className): array {
$rc = new ReflectionClass($className);
$routes = [];
$columns = [];
// 類別層級註解
foreach ($rc->getAttributes(Route::class) as $attr) {
$routes['class'] = $attr->newInstance();
}
// 屬性註解
foreach ($rc->getProperties() as $prop) {
foreach ($prop->getAttributes(Column::class) as $attr) {
$columns[$prop->getName()] = $attr->newInstance();
}
}
// 方法註解
foreach ($rc->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
foreach ($method->getAttributes(Route::class) as $attr) {
$routes['methods'][$method->getName()] = $attr->newInstance();
}
}
return ['routes' => $routes, 'columns' => $columns];
}
print_r(parseControllerAttributes(UserController::class));
/* 輸出:
Array
(
[routes] => Array
(
[class] => Route Object ( [path] => /api/users [methods] => Array ([0]=>GET [1]=>POST) [name]=>users.index )
[methods] => Array
(
[show] => Route Object ( [path] => /api/users/{id} [methods]=>Array([0]=>GET) [name]=>users.show )
)
)
[columns] => Array
(
[id] => Column Object ( [name]=>user_id [type]=>integer [nullable]=> )
[name] => Column Object ( [name]=>user_name [type]=>string [nullable]=> )
)
)
*/
?>
效能考量與快取
- 反射涉及文件系統讀取、AST 解析,**較慢** (微秒~毫秒級)。
- 生產環境**必須快取**反射結果 (如容器解析圖、路由表、實體映射)。
- 使用
opcache 可大幅加速反射 (類定義常駐記憶體)。
- 大規模掃描 (如掃描目錄所有類) 建議構建時預編譯 (如
composer dump-autoload -o、PHP-DI 定義檔)。
常見陷阱
ReflectionClass::newInstanceArgs() 參數順序必須與建構子一致,命名參數 (PHP 8) 不直接支援,需自行映射。
getType() 返回 ReflectionNamedType (PHP 8) 或 ReflectionType (PHP 7),判斷內建類型用 isBuiltin()。
- 靜態屬性
ReflectionProperty::getValue() 需傳 null 作為對象:$prop->getValue(null)。
getAttributes() 僅返回 #[\Attribute] 標記的類,傳統 DocBlock (/** @Route */) 需用 phpDocumentor/ReflectionDocBlock 解析。
反射賦予 PHP 動態語言的極致靈活性,是現代框架 (Laravel, Symfony, Hyperf) 自動裝配、路由、驗證、ORM 的基石。掌握核心 API、理解效能代價、善用快取與屬性註解,能讓你在框架擴展、舊系統重構、開發工具構建等場景中遊刃有餘。
- 上一篇文章: PHP 異常處理完整指南:錯誤與異常的統一捕獲、日誌記錄與優雅降級
- 下一篇文章: PHP 操作 MySQL 基礎類封裝:從 mysql_* 到 PDO 的現代化重構
Copyright ©
程式碼之家 All Rights Reserved