クラスの抽象化

PHP には、抽象クラス、抽象メソッド、抽象プロパティがあります。 abstract として定義された抽象クラスのインスタンスを生成することはできず、 1つ以上の抽象メソッドや抽象プロパティを含むクラスは抽象クラスでなければいけません。 abstract として定義されたメソッドは、そのメソッドのシグネチャと public または protected のアクセス権を宣言するのみで、 実装を定義することはできません。抽象プロパティは、 get や set の要件を宣言することができ、 実装はどちらか一方に対してのみ行えます。両方同時に実装することはできません。

抽象クラスから継承する際、親クラスの宣言で abstract としてマークされた 全てのメソッドは、子クラスで定義されなければなりません。加えて、 オブジェクトの継承 と シグネチャの互換性に関するルール に従わなければいけません。

PHP 8.4 から、抽象クラスは public または protected の抽象プロパティを宣言できるようになりました。 protected な抽象プロパティは、protected または public のスコープから読み書き可能なプロパティにより 要件が満たされます。

抽象プロパティは、通常のプロパティによって、または 必要な操作に対応した フック を定義したプロパティにより要件が満たされます。

例1 抽象メソッドの例

<?php

abstract class AbstractClass
{
    // 拡張クラスにこのメソッドの定義を強制する
    abstract protected function getValue();
    abstract protected function prefixValue($prefix);

    // 共通のメソッド
    public function printOut()
    {
        print $this->getValue() . "\n";
    }
}

class ConcreteClass1 extends AbstractClass
{
    protected function getValue()
    {
        return "ConcreteClass1";
    }

    public function prefixValue($prefix)
    {
        return "{$prefix}ConcreteClass1";
    }
}

class ConcreteClass2 extends AbstractClass
{
    public function getValue()
    {
        return "ConcreteClass2";
    }

    public function prefixValue($prefix)
    {
        return "{$prefix}ConcreteClass2";
    }
}

$class1 = new ConcreteClass1();
$class1->printOut();
echo $class1->prefixValue('FOO_'), "\n";

$class2 = new ConcreteClass2();
$class2->printOut();
echo $class2->prefixValue('FOO_'), "\n";

?>

上の例の出力は以下となります。

ConcreteClass1
FOO_ConcreteClass1
ConcreteClass2
FOO_ConcreteClass2

例2 抽象メソッドの例

<?php

abstract class AbstractClass
{
    // 抽象メソッドでは、必須の引数だけを定義しています
    abstract protected function prefixName($name);
}

class ConcreteClass extends AbstractClass
{
    // 子クラスでは、親のシグネチャにないオプションパラメータを定義することもあるでしょう
    public function prefixName($name, $separator = ".")
    {
        if ($name == "Pacman") {
            $prefix = "Mr";
        } elseif ($name == "Pacwoman") {
            $prefix = "Mrs";
        } else {
            $prefix = "";
        }

        return "{$prefix}{$separator} {$name}";
    }
}

$class = new ConcreteClass();
echo $class->prefixName("Pacman"), "\n";
echo $class->prefixName("Pacwoman"), "\n";

?>

上の例の出力は以下となります。

Mr. Pacman
Mrs. Pacwoman

例3 抽象プロパティの例

<?php

abstract class A
{
    // 継承するクラスは、public に読み取り可能なプロパティを持たなければなりません
    abstract public string $readable {
        get;
    }

    // 継承するクラスは、protected または public に書き込み可能なプロパティを持たなければなりません
    abstract protected string $writeable {
        set;
    }

    // 継承するクラスは、protected または public で読み書き可能なプロパティを持たなければなりません
    abstract protected string $both {
        get;
        set;
    }
}

class C extends A
{
    // 要件を満たし、さらに書き込みも可能にしているため有効です
    public string $readable;

    // public に読み取り可能でないため、要件を満たしません
    protected string $readable;

    // 要件を正確に満たしているため有効です
    // protected のスコープからのみ書き込みが可能です
    protected string $writeable {
        set => $value;
    }

    // protected から public にアクセス権を拡張しており、問題ありません
    public string $both;
}

?>

抽象プロパティにはフックを実装できます。 前の例のように、get または set のどちらかを、定義せず宣言のみ行います。

例4 フック付きの抽象プロパティの例

<?php

abstract class A
{
    // set のデフォルト実装(オーバーライド可能)を提供し、
    // 子クラスが get を実装するよう要求しています
    abstract public string $foo {
        get;

        set {
            $this->foo = $value;
        }
    }
}

?>