基礎

PHPの抽象クラス・抽象メソッド|abstractで設計の枠組みを定義する

PHPの抽象クラス(abstract class)は、直接インスタンス化できないクラスで、子クラスに実装を強制するための設計の枠組みです。abstract キーワードを付けて宣言し、抽象メソッド(中身のないメソッド)を持つことができます。

抽象クラスは「子クラスはこのメソッドを必ず実装しなさい」という契約を定めるもので、共通の処理と個別の処理を分離する設計パターンの基盤になります。

基本的な使い方

PHP
<?php
abstract class Shape {
    // 抽象メソッド:子クラスで必ず実装する
    abstract public function area(): float;
    abstract public function perimeter(): float;

    // 通常のメソッド:そのまま継承される
    public function describe(): string {
        return "面積: " . round($this->area(), 2) . "、周囲長: " . round($this->perimeter(), 2);
    }
}

class Circle extends Shape {
    public function __construct(private float $radius) {}

    public function area(): float {
        return M_PI * $this->radius ** 2;
    }

    public function perimeter(): float {
        return 2 * M_PI * $this->radius;
    }
}

class Rectangle extends Shape {
    public function __construct(
        private float $width,
        private float $height
    ) {}

    public function area(): float {
        return $this->width * $this->height;
    }

    public function perimeter(): float {
        return 2 * ($this->width + $this->height);
    }
}

// $shape = new Shape();  // Error: 抽象クラスはインスタンス化できない

$circle = new Circle(5);
$rect = new Rectangle(4, 6);

echo "円: " . $circle->describe() . "\n";
echo "長方形: " . $rect->describe() . "\n";
実行結果
円: 面積: 78.54、周囲長: 31.42
長方形: 面積: 24、周囲長: 20

抽象クラス Shapearea()perimeter() の実装を子クラスに強制します。describe() は共通処理として提供されます。

抽象クラスの特徴

PHP
<?php
abstract class Animal {
    // コンストラクタも持てる
    public function __construct(protected string $name) {}

    // 抽象メソッド
    abstract public function speak(): string;

    // 通常のメソッド
    public function introduce(): string {
        return "{$this->name}は「{$this->speak()}」と鳴きます";
    }

    // 静的メソッドも持てる
    public static function createRandom(): string {
        $types = ["Dog", "Cat"];
        return $types[array_rand($types)];
    }
}

class Dog extends Animal {
    public function speak(): string {
        return "ワンワン";
    }
}

class Cat extends Animal {
    public function speak(): string {
        return "ニャー";
    }
}

$dog = new Dog("ポチ");
$cat = new Cat("タマ");
echo $dog->introduce() . "\n";
echo $cat->introduce() . "\n";
実行結果
ポチは「ワンワン」と鳴きます
タマは「ニャー」と鳴きます

実用的な例

PHP
<?php
abstract class DataExporter {
    protected array $data;

    public function __construct(array $data) {
        $this->data = $data;
    }

    // テンプレートメソッド
    final public function export(): string {
        $header = $this->formatHeader();
        $body = $this->formatBody();
        return $header . "\n" . $body;
    }

    abstract protected function formatHeader(): string;
    abstract protected function formatBody(): string;
}

class CsvExporter extends DataExporter {
    protected function formatHeader(): string {
        if (empty($this->data)) return "";
        return implode(",", array_keys($this->data[0]));
    }

    protected function formatBody(): string {
        $lines = [];
        foreach ($this->data as $row) {
            $lines[] = implode(",", array_values($row));
        }
        return implode("\n", $lines);
    }
}

class JsonExporter extends DataExporter {
    protected function formatHeader(): string {
        return '{"data": [';
    }

    protected function formatBody(): string {
        $items = [];
        foreach ($this->data as $row) {
            $items[] = json_encode($row, JSON_UNESCAPED_UNICODE);
        }
        return implode(",\n", $items) . "\n]}";
    }
}

$data = [
    ["name" => "田中", "age" => 30],
    ["name" => "佐藤", "age" => 25],
];

echo "--- CSV ---\n";
echo (new CsvExporter($data))->export() . "\n\n";

echo "--- JSON ---\n";
echo (new JsonExporter($data))->export() . "\n";
実行結果
--- CSV ---
name,age
田中,30
佐藤,25

--- JSON ---
{"data": [
{"name":"田中","age":30},
{"name":"佐藤","age":25}
]}
抽象クラスとインターフェースの違い

抽象クラスは通常のメソッド(実装付き)も持てますが、インターフェースは基本的にメソッドのシグネチャのみです。抽象クラスは単一継承のみ、インターフェースは複数実装可能です。「共通処理+個別処理」なら抽象クラス、「契約の定義のみ」ならインターフェースを使い分けましょう。

注意

子クラスが抽象メソッドをすべて実装しないと、その子クラスも抽象クラスとして宣言する必要があります。また、抽象メソッドのアクセス修飾子は子クラスで同じかより緩い(publicなど)ものにする必要があります。

まとめ

  • abstract class は直接インスタンス化できない設計の枠組み
  • 抽象メソッドは子クラスで必ず実装しなければならない
  • 通常のメソッドやコンストラクタも定義でき、子クラスに共通処理を提供できる
  • テンプレートメソッドパターンとの相性が良い
  • 共通処理があるなら抽象クラス、契約のみならインターフェースを使う