My External Storage

Jan 29, 2020 - 3 minute read - Comments - php

PHPで独自クラスを使った型キャストをおこなう方法

PHPで型を使って安全かつIDEの支援を受けながら開発したい。
arrayやインターフェイスで受け取った引数に、型をキャストする方法を考えた。

TL;DR

  • PHPはスカラー型やobject型を使ったキャストしかサポートしていない
  • 戻り値に型を指定した関数を用意すれば独自型のキャストができる
  • PHP7.4からはアロー関数を使った簡易関数も作れる
  • 型を指定しておくとIDEに支援もフルに受けることができる
final class CastObject extends ParentObject
{
    // 型キャストを行なうだけの関数
    public static function cast($obj): self
    {
        if (!($obj instanceof self)) {
            throw new InvalidArgumentException("{$obj} is not instance of CastObject");
        }
        return $obj;
    }
}

// アロー関数を使ったキャスト関数
$cast = fn($orig): CastObject => $orig;

独自クラスへの型キャストがしたい。

php-goというPHP製のGoインタプリタを作っている。

インタプリタの実装をしていると、基となるインターフェイス(あるいはarray)としてオブジェクトを受け取り、再帰的な実際の具象クラスの種類ごとに処理を切り替えるような実装を行なう。
そのような処理を再帰的に繰り返すため、少しでも意図しないロジックパスを通りそうになったら(期待する具象クラスではなかったら)即例外を発生させたい。
また、IDEの支援をフルに受けるためには、きちんと型をキャストしたほうがよい。

独自型をキャストする方法

$casted = (MyClass)$obj;のように書けば終わり、だったら簡単なのだが、PHPのキャストはスカラー型やobject型でしか利用できない。

キャスト関数をつくり戻り値型を指定する

ではどうすればいいかと言うと、関数の戻り値に型を指定したキャスト用の関数を用意するのが一番簡単そうだった。

たとえば、こんな継承関係のParentObjectクラスとCastObjectクラスがあったとする。

class ParentObject{}

final class CastObject extends ParentObject
{
    public string $prop;

    public function __construct(string $prop)
    {
        $this->prop = $prop;
    }
}

あらかじめ、キャスト用の関数を用意しておく(多用するならば、クラスにstatic関数として用意すればよいだろう)。

final class CastObject extends ParentObject
{
    public static function cast($obj): self
    {
        if (!($obj instanceof self)) {
            throw new InvalidArgumentException("{$obj} is not instance of CastObject");
        }
        return $obj;
    }
}

どこからか受け取ったParentObjectオブジェクトにCastObjectクラスをキャストしたいならば、以下のように書けばよい。

// どこからか受け取ったParentbjectな $obj
if ($obj instanceof CastObject) {
    $casted = CastObject::cast($obj);
}

7.4からはアロー関数も利用できる

PHP7.4からはアロー関数という無名関数の書き方が用意されている。

何度も使わないようなキャストならば、その場で使い捨ての関数として宣言してもよいだろう。

$cast = fn($orig): CastObject => $orig;

// どこからか受け取ったParentbjectな $obj
if ($obj instanceof CastObject) {
    $casted = CastObject::cast($obj);
}

終わりに

明示的に型がわからなくてもメソッド呼び出しなどはエラーにならない。
ただ、IDEのサポートを受けたいならば明示的に型情報が付与されていたほうがよい。
また、型が自明だと安心して処理を続けられる。

参考

関連記事