type holyshared = Engineer<mixed>

技術的なことなど色々

HackでAttributeを表現するためのインターフェース

HHVM3.29より、Attributeを表現するインターフェースが追加されました。
今までも、Attribute自体は使用できていましたが、以下の問題がありました。

  • タイプチェックがかからない。
    • Attributeに依存する処理でバグがあると原因を特定しにくい。
  • ユーザー独自のAttributeの仕様をコードで表現できない。
    • 引数は2つなのか、型はstringなのか表現できないので、ドキュメントなどにまとめる必要があった。

この問題をHHVM3.29で組み込まれた、インターフェースで解決できるようになりました。
.hhconfiguser_attributes= を設定として追加した後に、下記のインターフェースを実装するだけで、Attributeが利用できる部分の指定、インターフェースを実装したクラスでのタイプチェックがかかります。

  • ClassAttribute - クラス
  • EnumAttribute - 列挙型
  • TypeAliasAttribute - 型の別名
  • FunctionAttribute - 関数
  • MethodAttribute - メソッド
  • InstancePropertyAttribute - インスタンスプロパティ
  • StaticPropertyAttribute - 静的プロパティ
  • ParameterAttribute - 引数

Attribute仕様の表現

例えば、クラスとメソッドに指定できるAttributeを表現したい場合、下記のようにインターフェースを指定します。

\HH\ClassAttribute, \HH\MethodAttribute をクラスに実装することで、クラスとメソッドに指定できる部分を制限できます。

<?hh //strict

namespace TypedUserAttributes;

final class Version implements \HH\EnumAttribute, \HH\ClassAttribute, \HH\MethodAttribute, \HH\FunctionAttribute {
  public function __construct(
    private float $version
  ) {
  }

  public function __toString() : string {
    return (string) $this->version;
  }
}

また、指定する場所を間違えたり、パラメータの型を間違えた場合、タイプチェックに引っかかることがわかります。

f:id:holyshared:20181223224854p:plain

Attributeの取得

Attributeの情報を取得する場合は、リフレクションを使用します。
メソッドとして、getAttributeClass が提供されているので、Attributeのクラス名を引数に渡して、Attributeのインスタンスを取得できます。

<?hh //strict

namespace TypedUserAttributes;

require_once 'Post.hh';
require_once 'Version.hh';

use \ReflectionClass;

<<__EntryPoint>>
function main(): noreturn {
  $class = new ReflectionClass(\TypedUserAttributes\Post::class);

  $classVersion = $class->getAttributeClass(\TypedUserAttributes\Version::class);
  \printf("class version: %s\n", $classVersion);

  $methodVersion = $class->getMethod('getTitle')->getAttributeClass(\TypedUserAttributes\Version::class);
  \printf("method version: %s\n", $methodVersion);

  exit();
}

最後に

インターフェースが追加されたことにより、パッケージを設計する開発者にとっては非常にありがたい恩恵を受けることができるようになりました。

これからはAttributeを使う際には、インターフェースを使用して欲しいです。

また、ここで紹介したコードはここに置いて起きました。 gist.github.com