WebエンジニアのLoL日記

LoLをプレイしたりLJLの試合を見たりするのが好きなエンジニア。LoLのイベントやパッチノートなど気になった点を記事にしたり、LJLについの記事をかいたりしています。某社でWeb系のエンジニアとして働いているので、技術系の記事もたまに書きます。コンタクトを取りたい場合はtwitterまで。

PHP7.4からクラスのプロパティに型がかけるようになる

f:id:yoshiki_utakata:20190111231914p:plain

はじめに

PHP 7.4の新機能としてクラスのプロパティに型がかけるようになる機能が提案されていました。これはTyped Properties 2.0と呼ばれるもの。日本語に治すとプロパティ型注釈といった感じでしょうか。仕様書は以下の通りです。

wiki.php.net

これが約1ヶ月前の2019年1月にマージされており、PHP 7.4で入ることが確定のようです。

github.com

以下、提案書を簡単に日本語で説明したものです。

機能の概要

スカラ型 *1 の型注釈の導入や、戻り値の型注釈の導入により、PHP7は型システムが非常に強化された。しかし現状クラスのプロパティには型をつけられないので、開発者はgetterやsetterをつかい、その引数や戻り値に型注釈をつけている。これはボイラーテンプレート *2 でありあまりよろしくない。

これを利用することで

class User {
    /** @var int $id */
    private $id;
    /** @var string $name */
    private $name;

    public function getId(): int {
        return $this->id;
    }
    public function setId(int $id): void {
        $this->id = $id;
    }
...
}

こう書かれていたのがこうなる

class User {
    public int $id;
    public string $name;
}

v1 との違い

実はプロパティ型注釈自体は提案されたことがある機能である*3

wiki.php.net

しかしこのv1の機能は却下されている。v1とv2は何が違うのか。大きく2つある。

  • staticプロパティもサポートしている。v1では実装上の問題で含まれていなかったらしい。
  • 型注釈されたプロパティの参照渡しにも対応している(詳細は後述)。

対応している型

class Example {
    // voidとcallable以外は対応している
    public int $scalarType;
    protected ClassName $classType;
    private ?ClassName $nullableClassType;
 
    // staticプロパティでもOK
    public static iterable $staticProp;
 
    // varでもOK
    var bool $flag;
 
    // デフォルト値も設定可能
    public string $str = "foo";
    public ?string $nullableStr = null;
 
    // こう書いた場合は$xも$yもfloat
    public float $x, $y;
}

callableについて

callableだけは扱いが文脈依存で非常に難しいので対応していません*4。これはcallableが「型のように使えるけど型ではない」性質によるもので、文脈に依存しないcallableが別で提案されています。Consistent Callablesというものです。

wiki.php.net

callableの代わりにClousureを使うという方法があります。

ちなみに対応している型は

  • bool, int, float, string, array, object
  • iterable
  • self, parent
  • クラス・インタフェース
  • ?bool のようなnullable型

strict_types

引数や返り値の型注釈と同様に strict_types=1/0 が効きます。

継承

当然ながら親クラスと同じ名前で違う型のプロパティは定義できません。

class A {
    private bool $a;
}
class B extends A {
    public string $a; // ダメ
}

トレイトも同様の制限を受けます。

将来の話: readonly

将来的には readonly みたいなものが導入されると本当にgetter/setterを作る文化はなくなりそうだ。みたいな話が出ている。はやくreadonlyつくってくれ。

値の初期化

nullableでない型にデフォルト値を設定しない場合

class Test {
    public int $n
}

$test = new Test();
echo $test->n; // ここで初めて型エラーになる

これは、クラスとかだとデフォルト値を設定できないから。

class Test {
    // ここではAnotherClassのデフォルト値を設定できない
    public AnotherClass $a;

    public function __construct() {
        // 呼ばれる前にsetされていればセーフ
        $this->a = new AnotherClass;
    }
}

$test = new Test();
$test->a; // aをsetしていなかったらここでエラーになる

nullableでない型のプロパティをunset仕様としてもエラーになる。

class Test {
    public int $val = 1;
}

$test = new Test(42);
var_dump($test->val);

参照渡し

$aryの型が変わらなければこういうことも可能です。

class Test {
    public array $ary = [3, 2, 1];
}
$test = new Test;
sort($test->ary);

さいごに

他にも細かい仕様がいろいろ書かれていますが、これ以上は知らなくても問題なく書けそう。 強いて言えばunsetした場合の挙動が気になったくらい。PHP7.4がリリースされたらぜひ使ってみてください。

参考

パーフェクトPHP (PERFECT SERIES 3)

パーフェクトPHP (PERFECT SERIES 3)

よくわかるPHPの教科書 【PHP7対応版】

よくわかるPHPの教科書 【PHP7対応版】

*1:intやstringなどPHP7から新しく引数の型注釈ができるようになった型

*2:金型。いつも同じように思考停止してgetterやsetterをはやしているのが良くないということである

*3:なので今回v2となっている

*4:例えば、privateメソッドはクラス内ではcallableだがクラス外ではcallableではない