博客 / 詳情

返回

推薦 PHP 屬性(Attributes) 簡潔讀取 API 擴展包

推薦 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-responsecachelaravel-event-sourcinglaravel-markdown,用於清理各代碼庫中積累屬性讀取相關的樣板代碼。

推薦 PHP 屬性(Attributes) 簡潔讀取 API 擴展包

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.