オブジェクト インターフェイス

オブジェクト インターフェースでは、クラスが実装すべきメソッドやプロパティの 宣言だけを行うコードを作成できます。ここでは具体的な実装は必要ありません。 インターフェイスはクラス、トレイト、列挙型と名前空間を共有するので、 それらと同じ名前を使ってはいけません。

インターフェイスは通常のクラスと同様に定義することができますが、 キーワード class のかわりに interface を用います。またメソッドの実装は全く定義しません。

インターフェイス内で宣言される全てのメソッドは public である必要があります。 これは、インターフェイスの特性によります。

インターフェイスには、ふたつの互いを補完する役割があります。

  • 同じインターフェイスを実装していることで、 開発者が交換可能な異なるクラスを作成できるようにします。 同じインターフェイスを持つクラスによくある例として、 複数のデータベースにアクセスするサービスや 決済のゲートウェイ、 異なるキャッシュ戦略が挙げられます。 実装が異なっていても、 それを使うコードに変更を加えることなく、それらを交換することができます。
  • メソッドや関数が、インターフェイスを満たす引数を受け付け、 操作できるようにします。 オブジェクトが何をするのかや、 どう実装されているのかを気にする必要はありません。 振る舞いの重要性を説明するために、 IterableCacheableRenderable のような名前が付けられることがよくあります。

インターフェイスは、 マジックメソッド を宣言しても問題ありません。

注意:

コンストラクタ をインターフェイスで宣言できますが、全くおすすめできません。 そうしてしまうと、 インターフェイスを実装するクラスの柔軟性が大きく損なわれる上、 コンストラクタには継承ルールが適用されないため、 一貫しない、予期せぬ結果を生む可能性があるからです。

implements

インターフェイスを実装するには、implements 演算子を使用し、 このインターフェイスに含まれる全てのメソッドを実装する必要があります。 実装されていない場合、致命的エラーとなります。 各インターフェイスをカンマで区切って指定することで、 クラスは複数のインターフェイスを実装することができます。

警告

インターフェイスを実装するクラスでは、 インターフェイス内の名前とは異なる名前を メソッドの引数に使うことができます。 しかし、PHP 8.0 以降では 名前付き引数 がサポートされています。 これは、メソッドをコールする側がインターフェイス内の名前に依存する可能性があるということです。 そうした理由から、開発者は実装されるインターフェイスと同じ引数名を使うことを強く推奨します。

注意:

クラスと同様、インターフェイスも extends 演算子で継承することができます。

注意:

インターフェイスを実装したクラスでは、 シグネチャの互換性に関するルール を守った形で、インターフェイス内の全てのメソッドを宣言しなければいけません。 クラスは、同じ名前のメソッドを定義した複数のインターフェイスを実装することが出来ます。 この場合、実装されるメソッドは全て、 シグネチャの互換性に関するルール に従わなければいけません。 そうすることで、共変性と反変性 のルールも適用できます。

定数

インターフェイスに定数を持たせることもできます。 インターフェイス定数は クラス定数 とまったく同じように動作します。 PHP 8.1.0 より前のバージョンでは、 そのインターフェイスを継承したクラスやインターフェイスから定数をオーバーライドすることができませんでした。

プロパティ

PHP 8.4.0 から、インターフェイスはプロパティを宣言できるようになりました。 宣言する場合、そのプロパティの読み取り可否、書き込み可否、あるいは双方を 指定する必要があります。 インターフェイスによる宣言は、public な読み書きに対してのみ適用できます。

クラス側でインターフェイスのプロパティ要件を満たすには、 public な通常のプロパティを定義する、 対応するフックを実装する public な 仮想プロパティ を定義するなど、複数の方法があります。 読み取り用プロパティは readonly プロパティによって満たすこともできます。 ただし、インターフェイスで「書き込み可能」と宣言されているプロパティを readonly にすることはできません。

例1 インターフェイスのでのプロパティ宣言の例

<?php
interface I
{
    // 実装クラスは、public に読み取り可能なプロパティを持つ必要があります。
    // public に書き込み可能かどうかは制限されません。
    public string $readable { get; }

    // 実装クラスは、public に書き込み可能なプロパティを持つ必要があります。
    // public に読み取り可能かどうかは制限されません。
    public string $writeable { set; }

    // 実装クラスは、public に読み書き可能なプロパティを持つ必要があります。
    public string $both { get; set; }
}

// このクラスは、3つのプロパティをすべてフックのない通常のプロパティとして実装しています。
// これは問題ありません。
class C1 implements I
{
    public string $readable;

    public string $writeable;

    public string $both;
}

// このクラスは、3つのプロパティすべてを、要求されたフックのみを使って実装しています。
// これも問題ありません。
class C2 implements I
{
    private string $written = '';
    private string $all = '';

    // get フックのみ使い仮想プロパティを作成しています。
    // 「public に読み取り可能」という要件を満たします。
    // 書き込みはできませんが、インターフェイスからは要求されていません。
    public string $readable { get => strtoupper($this->writeable); }

    // インターフェイスはこのプロパティが「書き込み可能」であることのみ要求しています。
    // ここに get の動作も含めることは全く問題ありません。
    // この例では仮想プロパティを作成しています。
    public string $writeable {
        get => $this->written;
        set {
            $this->written = $value;
        }
    }

    // このプロパティは「public に読み書き可能」でなければならないため、
    // get と set の両方を実装する必要があります。
    // デフォルトの動作のままでも問題ありません。
    public string $both {
        get => $this->all;
        set {
            $this->all = strtoupper($value);
        }
    }
}
?>

例2 インターフェイスの例

<?php

// インターフェイス 'Template' を宣言する
interface Template
{
    public function setVariable($name, $var);
    public function getHtml($template);
}

// インターフェイスを実装する。
// これは動作します。
class WorkingTemplate implements Template
{
    private $vars = [];
  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
  
    public function getHtml($template)
    {
        foreach($this->vars as $name => $value) {
            $template = str_replace('{' . $name . '}', $value, $template);
        }
 
        return $template;
    }
}

// これは動作しません。
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (Template::getHtml)
class BadTemplate implements Template
{
    private $vars = [];
  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
}
?>

例3 インターフェイスの継承

<?php
interface A
{
    public function foo();
}

interface B extends A
{
    public function baz(Baz $baz);
}

// これは動作します。
class C implements B
{
    public function foo()
    {
    }

    public function baz(Baz $baz)
    {
    }
}

// これは動作せず、fatal error となります。
class D implements B
{
    public function foo()
    {
    }

    public function baz(Foo $foo)
    {
    }
}
?>

例4 共変性を保った形で、複数のインターフェイスを実装する

<?php
class Foo {}
class Bar extends Foo {}

interface A {
    public function myfunc(Foo $arg): Foo;
}

interface B {
    public function myfunc(Bar $arg): Bar;
}

class MyClass implements A, B
{
    public function myfunc(Foo $arg): Bar
    {
        return new Bar();
    }
}
?>

例5 複数のインターフェイスの継承

<?php
interface A
{
    public function foo();
}

interface B
{
    public function bar();
}

interface C extends A, B
{
    public function baz();
}

class D implements C
{
    public function foo()
    {
    }

    public function bar()
    {
    }

    public function baz()
    {
    }
}
?>

例6 インターフェイスでの定数

<?php
interface A
{
    const B = 'Interface constant';
}

// Interface constant と表示します。
echo A::B;


class B implements A
{
    const B = 'Class constant';
}

// Class constant と表示します。
// PHP 8.1.0 より前のバージョンでは、定数をオーバライドできなかったため、これは動作しませんでした。
echo B::B;
?>

例7 抽象クラスとインターフェイス

<?php
interface A
{
    public function foo(string $s): string;

    public function bar(int $i): int;
}

// 抽象クラスは、インターフェイスの一部のみを実装しても構いません。
// 抽象クラスを継承したクラスは、未実装の残りのメソッドを実装しなければなりません。
abstract class B implements A
{
    public function foo(string $s): string
    {
        return $s . PHP_EOL;
    }
}

class C extends B
{
    public function bar(int $i): int
    {
        return $i * 2;
    }
}
?>

例8 継承と実装を同時に行う

<?php

class One
{
    /* ... */
}

interface Usable
{
    /* ... */
}

interface Updatable
{
    /* ... */
}

// ここでは、キーワードの順番が重要です。
// 'extends' を始めに置かなければいけません。
class Two extends One implements Usable, Updatable
{
    /* ... */
}
?>

インターフェイスと型宣言を組み合わせると、 特定のオブジェクトに特定のメソッドをうまく持たせることができます。 instanceof 演算子および 型宣言 を参照ください。