PHPのインターフェースは、クラスが提供すると約束するメソッドを、コードなしで宣言します:interface Shape { public function area(): float; }。implements Shapeと書いたクラスはarea()を定義しなければならず、Shapeで型宣言した関数はどれもそのクラスを受け付けます。
describe()はどのクラスを受け取るかを知りませんし、気にもしません。インターフェースの約束だけに頼っているので、新しいTriangle implements Shapeも1行も変えずに使えます。
インターフェースに対してプログラムする
インターフェースを書くよくある理由は、コードが特定のクラスではなく能力に依存できるようにすることです。ここでは注文サービスがどんなNotifierでも受け付けるので、同じコードで本番ではメールを送り、テストではメッセージを集められます。
複数のインターフェースを実装する
クラスが継承できる親は1つだけですが、インターフェースは必要なだけ実装できます。implementsのあとにカンマで区切って並べ、そのすべてのメソッドを定義します。
JsonSerializableはPHPの組み込みインターフェースの1つで、実装するとそのオブジェクトに対してjson_encode()が何を出力するかを制御できます。
インターフェースの定数
インターフェースは定数を宣言できます。実装するクラスはすべてそれを受け継ぎ、インターフェース名でもクラス名でも読めます。
PHP 8.3では、インターフェースが持てるのはメソッドと定数だけで、プロパティは持てません。メソッドはstaticでもかまいません。インターフェースにpublic static function fromArray(array $data): static;と書けば、各クラスにその静的メソッドの定義を義務づけられます。関連する決まった値の集合には、インターフェースの定数を並べるよりenumのほうが合っていることがよくあります。
インターフェースはほかのインターフェースを継承できる
インターフェースもextendsを使い、クラスと違って一度に複数を継承できます。子のインターフェースを実装するクラスは、連鎖全体のメソッドを定義しなければなりません。
組み込みインターフェース:Countable、IteratorAggregate、Stringable
PHPには、オブジェクトを言語の機能に結びつけるインターフェースが用意されています。Countableはcount($obj)を、IteratorAggregateはforeachを動くようにし、Stringableは__toString()メソッドを持つすべてのクラスで自動的に実装されます。
メソッドの書き忘れ
インターフェースを実装すると宣言したクラスがメソッドを1つでも欠いていると、PHPは足りないメソッドの名前を示す致命的エラーで止まります。
total()メソッドを加えればスクリプトは動きます。抽象クラスならインターフェースのメソッドを未定義のままにでき、これがエラーメッセージの示す逃げ道です。
インターフェースと抽象クラスの違い
どちらもクラスが持つべきメソッドを定義し、どちらも型宣言に使えます。違いは、ほかに何を含められるかと、1つのクラスがいくつ使えるかです。
| インターフェース | 抽象クラス | |
|---|---|---|
| メソッドの本体 | なし(シグネチャのみ) | 抽象メソッドと完成したメソッド |
| プロパティ | なし | あり |
| コンストラクタ | 宣言できるがあまり役に立たない | あり、コード付き |
| メソッドの可視性 | publicのみ | public、protected、private |
| 定数 | あり | あり |
| 1クラスあたりの数 | いくつでも(implements A, B, C) | 1つ(extends Base) |
| 典型的な用途 | 能力:Exportable、Countable | ファミリーの共有コード:Model、Controller |
2つはうまく組み合わせられます。型宣言用にインターフェースを定義し、共通のコードでそれを実装する抽象基底クラスを用意します。別の基底クラスが必要なクラスは、今まで通りインターフェースを直接実装できます。
よくある質問
PHPのインターフェースとは何ですか?
本体のないpublicなメソッドのシグネチャの一覧です:interface Logger { public function log(string $msg): void; }。implements Loggerと書いたクラスはその中のメソッドをすべて定義しなければならず、Loggerで型宣言したコードはどれもそのクラスを受け付けます。
PHPのクラスは複数のインターフェースを実装できますか?
できます。implementsのあとにカンマで区切って並べます:class File implements Readable, Writable, Countable。クラスはそのすべてのメソッドを定義する必要があります。ほかの言語で多重継承がしていることを、PHPはこの方法でまかないます。
PHPのインターフェースと抽象クラスの違いは何ですか?
インターフェースはpublicなメソッド(と定数)を宣言するだけで、プロパティもメソッドのコードも持たず、クラスはいくつでも実装できます。抽象クラスは抽象メソッドの隣にプロパティ、コンストラクタ、完成したメソッドを持てますが、クラスが継承できるのは1つだけです。能力(「エクスポートできる」)にはインターフェースを、クラスのファミリーで共有するコードには抽象クラスを使います。
PHPのインターフェースはプロパティを持てますか?
PHP 8.3では持てません。インターフェースに書けるのはメソッドのシグネチャと定数(const VERSION = 2;)だけで、プロパティは書けません。インターフェースでのプロパティの宣言(プロパティフック)はPHP 8.4で登場しました。
PHPでオブジェクトがインターフェースを実装しているか確認するには?
instanceofを使います:オブジェクトのクラスがそのインターフェースを直接、または親クラスを通して実装していれば、$obj instanceof Countableはtrueです。class_implements($obj)は、クラスが実装しているすべてのインターフェースの名前を返します。