推薦 PHP 屬性(Attributes) 簡潔讀取 API 擴展包
PHP 8.0 引入的 Attributes(屬性)為類、方法、屬性、常量和參數添加結構化元數據提供了便利方式。儘管概念設計合理,但讀取這些屬性所需的反射 API 卻顯得過於冗長。原本簡單的一行操作,往往要寫成多行樣板代碼。若需在某個類中查找某屬性的全部使用位置,還得編寫層層嵌套的循環。
Spatie 近期發佈的 php-attribute-reader 包提供了一套乾淨的靜態 API,專門解決上述問題。
使用 Attribute Reader
假設有一個攜帶 Route 屬性的控制器,目標是獲取該屬性的實例。使用原生 PHP 反射的寫法如下:
$reflection = new ReflectionClass(MyController::class);
$attributes = $reflection->getAttributes(Route::class, ReflectionAttribute::IS_INSTANCEOF);
$route = null;
if (count($attributes) > 0) {
$route = $attributes[0]->newInstance();
}
這段代碼長達五行,且仍需處理屬性不存在的情況。使用 php-attribute-reader 後簡化為:
use Spatie\Attributes\Attributes;
$route = Attributes::get(MyController::class, Route::class);
單行完成。屬性不存在時返回 null,無需額外的異常處理。
讀取方法屬性
從方法讀取屬性時,原生反射的繁瑣程度進一步加劇。以下示例試圖獲取控制器 index 方法的 Route 屬性:
$reflection = new ReflectionMethod(MyController::class, 'index');
$attributes = $reflection->getAttributes(Route::class, ReflectionAttribute::IS_INSTANCEOF);
$route = null;
if (count($attributes) > 0) {
$route = $attributes[0]->newInstance();
}
樣板代碼重複出現,僅反射類有所不同。該包通過專用方法統一處理各類目標:
Attributes::onMethod(MyController::class, 'index', Route::class);
Attributes::onProperty(User::class, 'email', Column::class);
Attributes::onConstant(Status::class, 'ACTIVE', Label::class);
Attributes::onParameter(MyController::class, 'show', 'id', FromRoute::class);
全類掃描
原生反射在整類範圍內查找屬性時最為繁瑣。假設某表單類的多個屬性均攜帶 Validate 屬性,原生 PHP 的實現大致如下:
$results = [];
$class = new ReflectionClass(MyForm::class);
foreach ($class->getProperties() as $property) {
foreach ($property->getAttributes(Validate::class, ReflectionAttribute::IS_INSTANCEOF) as $attr) {
$results[] = ['attribute' => $attr->newInstance(), 'target' => $property];
}
}
foreach ($class->getMethods() as $method) {
foreach ($method->getAttributes(Validate::class, ReflectionAttribute::IS_INSTANCEOF) as $attr) {
$results[] = ['attribute' => $attr->newInstance(), 'target' => $method];
}
foreach ($method->getParameters() as $parameter) {
foreach ($parameter->getAttributes(Validate::class, ReflectionAttribute::IS_INSTANCEOF) as $attr) {
$results[] = ['attribute' => $attr->newInstance(), 'target' => $parameter];
}
}
}
foreach ($class->getReflectionConstants() as $constant) {
foreach ($constant->getAttributes(Validate::class, ReflectionAttribute::IS_INSTANCEOF) as $attr) {
$results[] = ['attribute' => $attr->newInstance(), 'target' => $constant];
}
}
對於常見的屬性掃描需求,上述代碼量顯得過於龐大。使用該包後簡化為:
$results = Attributes::find(MyForm::class, Validate::class);
foreach ($results as $result) {
$result->attribute; // 已實例化的屬性對象
$result->target; // 反射對象
$result->name; // 例如 'email', 'handle.request'
}
所有返回的屬性均為實例化對象,子類通過 IS_INSTANCEOF 自動匹配,目標不存在時返回 null 而非拋出異常。
實際應用
該包已被 Spatie 旗下的多個開源項目採用,包括 laravel-responsecache、laravel-event-sourcing 和 laravel-markdown,用於清理各代碼庫中積累屬性讀取相關的樣板代碼。
推薦 PHP 屬性(Attributes) 簡潔讀取 API 擴展包