(jp) =
PHP 8 以降、属性を使用できるようになります。 これらの属性 (他の多くの言語ではアノテーションとも呼ばれます) の目的は、メタデータをクラス、メソッド、変数などに追加することです。 構造化された方法で。
属性の概念はまったく新しいものではありません。何年もの間、docblock を使用してその動作をシミュレートしてきました。 しかし、属性が追加されたことで、手動で docblock を解析する代わりに、この種のメタデータを表現するための言語の第一級市民ができました。
それで、彼らはどのように見えますか? カスタム属性を作成するにはどうすればよいですか? 注意事項はありますか? これらは、この投稿で回答される質問です。 飛び込みましょう!
# 流れ落ちる
まず最初に、実際の属性は次のようになります。
use \Support\Attributes\ListensTo;
class ProductSubscriber
public function onProductCreated(ProductCreated $event)
public function onProductDeleted(ProductDeleted $event)
この投稿の後半で他の例を示しますが、イベント サブスクライバーの例は、最初に属性の使用法を説明するのに適していると思います。
また、はい、私は知っています。構文は、あなたが望んでいた、または望んでいたものではないかもしれません。 あなたは好んだかもしれません @
、 また @:
、または docblocks または、 … しかし、それはここにとどまるため、対処することを学ぶほうがよいでしょう。 構文について言及する価値がある唯一のことは、すべてのオプションが説明されているということです。この構文が選択されたのには十分な理由があります。 内部リストで RFC に関する議論全体を読むことができます。
そうは言っても、クールなものに焦点を当てましょう。 ListensTo
フードの下で働く?
まず第一に、カスタム属性は単純なクラスであり、自身に Attribute
属性; このベース Attribute
と呼ばれていた PhpAttribute
元の RFC では変更されましたが、その後別の RFC で変更されました。
これは次のようになります。
class ListensTo
public string $event;
public function __construct(string $event)
$this->event = $event;
それだけです — とても簡単ですよね? 属性の目的を念頭に置いてください。属性はクラスとメソッドにメタデータを追加することを意図しており、それ以上のものではありません。 たとえば、引数の入力検証などには使用しないでください (使用できません)。 つまり、メソッドの属性内でメソッドに渡されたパラメーターにアクセスすることはできません。 この動作を許可する以前の RFC がありましたが、この RFC は明確に物事をよりシンプルに保ちました。
イベント サブスクライバーの例に戻ります。メタ データを読み取り、どこかに基づいてサブスクライバーを登録する必要があります。 Laravel のバックグラウンドを持っているので、これを行う場所としてサービス プロバイダーを使用しますが、他のソリューションを自由に思いつくことができます。
ちょっとしたコンテキストを提供するために、退屈なボイラープレートのセットアップを次に示します。
class EventServiceProvider extends ServiceProvider
private array $subscribers = [
ProductSubscriber::class,
];
public function register(): void
$eventDispatcher = $this->app->make(EventDispatcher::class);
foreach ($this->subscribers as $subscriber)
foreach (
$this->resolveListeners($subscriber)
as [$event, $listener]
)
$eventDispatcher->listen($event, $listener);
次の場合に注意してください。 [$event, $listener]
構文に慣れていない場合は、配列の分割に関する私の投稿で理解を深めることができます。
それでは見てみましょう resolveListeners
、魔法が起こる場所です。
private function resolveListeners(string $subscriberClass): array
$reflectionClass = new ReflectionClass($subscriberClass);
$listeners = [];
foreach ($reflectionClass->getMethods() as $method)
$attributes = $method->getAttributes(ListensTo::class);
foreach ($attributes as $attribute)
$listener = $attribute->newInstance();
$listeners[] = [
$listener->event,
[$subscriberClass, $method->getName()],
];
return $listeners;
docblock 文字列を解析するよりも、この方法でメタデータを読み取る方が簡単であることがわかります。 ただし、調べる価値のある複雑な点が 2 つあります。
まず、 $attribute->newInstance()
電話。 これは実際には、カスタム属性クラスがインスタンス化される場所です。 サブスクライバー クラスの属性定義にリストされているパラメーターを取得し、コンストラクターに渡します。
つまり、技術的には、カスタム属性を作成する必要さえありません。 あなたは呼び出すことができます $attribute->getArguments()
直接。 さらに、クラスをインスタンス化するということは、コンストラクターの柔軟性があり、好きな方法で入力を解析できることを意味します。 全体として、常に次を使用して属性をインスタンス化するのが良いと思います newInstance()
.
言及する価値のある2番目のことは、の使用です ReflectionMethod::getAttributes()
、メソッドのすべての属性を返す関数。 出力をフィルタリングするために、2 つの引数を渡すことができます。
ただし、このフィルタリングを理解するには、まず属性について知っておく必要があることがもう 1 つあります。 これはあなたには明らかだったかもしれませんが、とにかく簡単に言及したかったのです。同じメソッド、クラス、プロパティ、または定数に複数の属性を追加することが可能です。
たとえば、次のようにします。
Route(Http::POST, '/products/create'),
Autowire,
class ProductsCreateController
public function __invoke()
そう考えると理由は明らか Reflection*::getAttributes()
配列を返すので、その出力をフィルタリングする方法を見てみましょう。
コントローラーのルートを解析しているとします。関心があるのは、 Route
属性。 そのクラスをフィルターとして簡単に渡すことができます。
$attributes = $reflectionClass->getAttributes(Route::class);
2 番目のパラメーターは、そのフィルタリングの実行方法を変更します。 通行できます ReflectionAttribute::IS_INSTANCEOF
これは、特定のインターフェイスを実装するすべての属性を返します。
たとえば、いくつかの属性に依存するコンテナ定義を解析しているとします。次のようにできます。
$attributes = $reflectionClass->getAttributes(
ContainerAttribute::class,
ReflectionAttribute::IS_INSTANCEOF
);
これは、コアに組み込まれている優れた速記です。
# 技術理論
属性が実際にどのように機能するかがわかったので、次はさらに理論を考えて、属性を完全に理解できるようにします。 まず、これについては前に簡単に説明しましたが、属性はいくつかの場所に追加できます。
クラスだけでなく、匿名クラスでも。
class MyClass
$object = new
プロパティと定数。
public int $foo;
public const BAR = 1;
メソッドと関数;
public function doSomething(): void
function foo()
クロージャーと同様に。
$closure =
メソッドと関数のパラメーター。
function foo(ArgumentAttribute $bar)
docblock の前または後に宣言できます。
public function doSomething(): void
また、属性のコンストラクターによって定義される 1 つまたは複数の引数を取ることができます。
属性に渡すことができる許可されたパラメーターについては、クラス定数、 ::class
名前とスカラー型が許可されます。 ただし、これについてはもう少し説明が必要です。属性は定数式のみを入力引数として受け入れます。
これは、スカラー式が許可されていることを意味します (ビット シフトも含む)。 ::class
、定数、配列および配列のアンパック、ブール式、および null 合体演算子。 定数式として使用できるすべてのリストは、ソース コードにあります。
# 属性設定
デフォルトでは、上記のように、いくつかの場所に属性を追加できます。 ただし、特定の場所でのみ使用できるように構成することは可能です。 たとえば、次のようにすることができます ClassAttribute
クラスでのみ使用でき、それ以外の場所では使用できません。 この動作をオプトインするには、フラグを Attribute
属性クラスの属性。
次のようになります。
class ClassAttribute
次のフラグを使用できます。
Attribute::TARGET_CLASS
Attribute::TARGET_FUNCTION
Attribute::TARGET_METHOD
Attribute::TARGET_PROPERTY
Attribute::TARGET_CLASS_CONSTANT
Attribute::TARGET_PARAMETER
Attribute::TARGET_ALL
これらはビットマスク フラグであるため、バイナリ OR 演算を使用して組み合わせることができます。
class ClassAttribute
もう 1 つの構成フラグは、再現性に関するものです。 デフォルトでは、特に繰り返し可能としてマークされていない限り、同じ属性を 2 回適用することはできません。 これは、ターゲット コンフィギュレーションと同じ方法で、ビット フラグを使用して実行されます。
class ClassAttribute
これらのフラグはすべて、呼び出し時にのみ検証されることに注意してください $attribute->newInstance()
、以前ではありません。
# 組み込み属性
ベース RFC が受け入れられると、組み込み属性をコアに追加する新しい機会が生まれました。 そのような例の 1 つは、 Deprecated
属性であり、一般的な例は Jit
属性 — 最後の属性が何についてのものかわからない場合は、JIT とは何かについての私の投稿を読むことができます。
今後、ビルトイン属性がますます増えると確信しています。
最後に、ジェネリクスについて心配している方のために: ジェネリックが PHP に追加されたとしても、構文が競合することはないので、安全です!
属性の使用例をいくつか考えていますが、あなたはどうですか? PHP 8 のこの素晴らしい新機能について共有したい考えがある場合は、私に連絡してください。 ツイッター または電子メールで、または Reddit で話し合うことができます。