PHP 8 で導入される Attributes 事始め

## Attributes とは

紆余曲折[^1]ありましたが、 PHP 8 で `Attributes` という機能が提供されることが決まりました。

[^1]: [PHP7.1 で導入しようとした](https://wiki.php.net/rfc/attributes)けど採択されなかったり、 `@@Attr`, `<<Attr>>` など様々な記法が入れ代わり立ち代わり採択されて、alpha/beta 段階ではそれぞれのバージョンで異なる実装がなされていました。

この `Attributes` とは、

- Java で言う `Annotations`
- C#, C++, Rust, Hack で言う `Attributes`
- Python, JavaScript で言う `Decorators`

を指します。つまりどんなコードかというと、下記です。

```php
return
// ↓コレ
#[Get('/')]
fn() => ['Hello' => 'World'];
```

記法はPHP7以下では行コメントとみなされるので、シンタックスハイライトで強調はされませんがエラーにもなりません。地味にうれしい。

似たようなものは既に見たことがあると思います。

```php:BlogController.php

use Symfony\Component\Routing\Annotation\Route;

class BlogController
{
  // ↓コレ
  /**
   * @Route("/blog", name="blog_list")
   */
  public function list()
  {
    return [];
  }
}
```

これは現在 **「アノテーション」** と呼ばれ、 [doctrine/annotations](https://packagist.org/packages/doctrine/annotations) を実装として使われることが非常に多いです(依存パッケージが[約1,500個](https://packagist.org/packages/doctrine/annotations/dependents?order_by=downloads))。既に展開されているアノテーションと混同しないようにするため、アトリビュートと名付けられたようです。

簡単に言えば、 **「クラスや関数、メソッドやオブジェクトなどにメタ情報を付加する機能」** です。

これで何を実現出来るかというと、 **「フレームワークやライブラリが、アプリケーションコードに付加されたメタ情報を読み取ることで、アプリケーションコードのロジックを調整する」** ことが出来るようになります。

サンプルでは Symfony のルーティングで「/blog パスは BlogController::list メソッドを利用するよ」というメタ情報が付加されています。

また、別のサンプルとして [doctrine/orm](https://packagist.org/packages/doctrine/orm) での利用方法を見てみましょう。

```php:Product.php
<?php

use Doctrine\ORM\Annotation as ORM;

/**
 * @ORM\Entity @ORM\Table(name="products")
 **/
class Product
{
    /** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue **/
    private $id;

    /** @ORM\Column(type="string") **/
    private $name;

    // .. (other code)
}
```

このクラスがエンティティであること、DBのカラムとプロパティを紐づけることなどがメタ情報(コメントアノテーション)として書かれています。

## Attributes が実装されて何がうれしい？

既にコメントアノテーションが多くの PHP プロジェクトで利用されていて、機能的にはほとんど同じに見えます。では、何故今さら言語機能として Attributes が採用されたのでしょうか。

### パフォーマンスの向上

アノテーションの Doc コメントは文字列で取得することしか出来ず、パースするのに時間がかかります。そのため doctrine/annotations では一度取得したアノテーションをキャッシュすることが可能になっています。

Attributes の形式だと `Reflection` から取得出来るようになるので、パースの時間を省略することが出来ます。

### 取得容易性の向上

`ReflectionClass::getAttributes()` などのメソッドで、 `Reflection` から簡単に Attributes を取り出せるようになったので、 Doc コメントアノテーションに比べ取得が格段に簡単になります。

### 明確な型インスタンス

`Attribute::newInstance()` メソッドを使って `Attribute` クラスのインスタンスを生成することが出来るため、明確な型を使って利用ロジックを実装することが出来ます。そのため、静的解析やIDE補完などが容易に行えるようになります。

### PHP Core や Extensions での利用

PHP拡張の実装時に Attributes を使うことによって、拡張機能により実装された Attributes をアプリケーションコードに落とし込むことが出来るようになります。

```php
// 例えば
use Php\Attributes\Deprecated;

#[Deprecated("Use bar() instead")]
function foo() {}

foo();
// PHP Deprecated: Function foo is deprecated in test.php on line 5
```

このように、以前は Doc コメントで拘束性のなかった機能を、実際にランタイムでエラー出力出来るような形式に変えることが出来ます。

```php
// 現状 Attributes なしで記述した場合
class RequestSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        // 分かりづらい static 実装です
        return [RequestEvent::class => 'onKernelRequest'];
    }
 
    public function onKernelRequest(RequestEvent $event)
    {
    }
}

// Attributes を使うと
#[Attribute]
class Listener
{
    public $event;
 
    public function __construct(string $event)
    {
        $this->event = $event;
    }
}
 
class RequestSubscriber
{
    // このメソッドが RequestEvent を listen していることが明白です
    #[Listener(RequestEvent::class)]
    public function onKernelRequest(RequestEvent $event)
    {
    }
}
```

このように、 Attributes を活用することにより、実装に型安全な拡張性を持たせることが出来るようになります。

## 誰が Attributes を実装するの？

主なユースケースとして提案されているのが、

- PHP Core 実装として
- PHP extension 実装として
- 各種ライブラリ実装として
- 各種フレームワーク実装として

です。つまりアプリケーションコード側は Attributes を実装する機会はあまりないかなと思います(もちろん利用するケースは増えていくと思います)。

なのでアプリケーションコードを実装する人は、 Attributes の細かい実装などに詳しくなる必要はありません。

ライブラリを実装している人などは、 PHP 8 時代に向けて Attributes をどう使えば便利になるかなーと研究すると良いと思います。