te14

PHP 8: 属性 – Stitcher.io

(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
);

これは、コアに組み込まれている優れた速記です。

tpyoに気づきましたか? PR を送信して修正することができます。 このブログの最新情報を知りたい場合は、私をフォローしてください。 ツイッター または私のニュースレターを購読してください:

# 技術理論

属性が実際にどのように機能するかがわかったので、次はさらに理論を考えて、属性を完全に理解できるようにします。 まず、これについては前に簡単に説明しましたが、属性はいくつかの場所に追加できます。

クラスだけでなく、匿名クラスでも。


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 で話し合うことができます。

次の投稿
世界最古のピラミッドに関する 5 つの興味深い事実
前の投稿
アーマード コア 6 はオープンワールドではなく、クラシックなミッション ベースのデザインを使用します

ノート:

AZ: 動物の世界、ペット、ペット、野生の自然に関するカテゴリー記事…
SP:スポーツカテゴリー。
New vs Ne: ニュースコラム。
Te: テクノロジー カテゴリ。
Gt:エンターテインメントカテゴリー。
Bt: 占い、星占い、超常現象、超常現象。
Ta:人生コラム。