【PHP】PHPの継承・インターフェースをわかりやすく解説

phpアイキャッチ PHP

こんにちは、かつコーチです。

前回はクラスとインスタンスの基本を扱いました。

「クラスは設計図、インスタンスはその実体」という考え方には慣れてきたでしょうか。

今回は、クラスをさらに便利に使うための「継承」と「インターフェース」を解説します。

似たような仕組みなのに違いが分かりにくく、私も最初はかなり混乱したテーマなので、丁寧に整理していきます。

継承とは?

継承の基本的な考え方

継承とは、あるクラス(親クラス)の性質を引き継いで、新しいクラス(子クラス)を作る仕組みです。

たとえば「動物」という共通の性質を持つ「犬」と「猫」を作る場合を考えてみます。

継承を使わないと、似たようなコードを何度も書くことになります。

❌ Before:継承を使わず、同じコードを繰り返す

<?php
class Dog
{
    public string $name;

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

    public function eat(): string
    {
        return $this->name . 'がごはんを食べた';
    }

    public function bark(): string
    {
        return 'ワンワン!';
    }
}

class Cat
{
    public string $name;

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

    public function eat(): string
    {
        return $this->name . 'がごはんを食べた';
    }

    public function meow(): string
    {
        return 'ニャー!';
    }
}
?>

$name プロパティや eat() メソッドが、DogCat でまったく同じ内容なのに二重に書かれています。

こうなると、eat() の処理を修正するときに、両方のクラスを直す必要が出てきてミスの原因になります。

extends で共通部分をまとめる

✅ After:Animal クラスを継承して共通部分をまとめる

<?php
class Animal
{
    public string $name;

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

    public function eat(): string
    {
        return $this->name . 'がごはんを食べた';
    }
}

class Dog extends Animal
{
    public function bark(): string
    {
        return 'ワンワン!';
    }
}

class Cat extends Animal
{
    public function meow(): string
    {
        return 'ニャー!';
    }
}

$dog = new Dog('ポチ');
echo $dog->eat();  // ポチがごはんを食べた(親クラスのメソッド)
echo $dog->bark(); // ワンワン!(子クラス独自のメソッド)
?>

extends Animal と書くだけで、DogCatAnimal$name プロパティと eat() メソッドをそのまま使えるようになります。

共通処理は親クラスに1箇所だけ書けばよいので、修正漏れが起きにくくなります。

メソッドの上書き(オーバーライド)

親クラスの処理を子クラスで変更する

子クラスで同じ名前のメソッドを定義すると、親クラスのメソッドを上書きできます。

これをオーバーライドと呼びます。

<?php
class Animal
{
    public string $name;

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

    public function eat(): string
    {
        return $this->name . 'がごはんを食べた';
    }
}

class Cat extends Animal
{
    // 親クラスのeat()を上書き
    public function eat(): string
    {
        return $this->name . 'が魚を食べた';
    }
}

$cat = new Cat('タマ');
echo $cat->eat(); // タマが魚を食べた(子クラスの内容が優先される)
?>

parent:: で親クラスの処理を活かす

親クラスの処理を完全に置き換えるのではなく、「親の処理に追加したい」場合は parent:: を使います。

<?php
class Cat extends Animal
{
    public function eat(): string
    {
        $base = parent::eat(); // 親クラスのeat()を呼び出す
        return $base . '(お魚が大好き)';
    }
}

$cat = new Cat('タマ');
echo $cat->eat(); // タマがごはんを食べた(お魚が大好き)
?>

parent::eat() で親クラスの処理結果を受け取ってから、子クラス独自の処理を追加できます。

インターフェースとは?

「実装のルール」を決める仕組み

インターフェースとは、クラスが「どんなメソッドを持っていなければならないか」というルールだけを定義する仕組みです。

継承と違い、インターフェースには処理の中身(実装)は書けません。

<?php
interface Payable
{
    public function pay(int $amount): string;
}
?>

Payable インターフェースは、「pay() というメソッドを持つこと」だけを約束します。

implements でインターフェースを実装する

このインターフェースを、実際のクラスで実装implements)します。

<?php
interface Payable
{
    public function pay(int $amount): string;
}

class CreditCardPayment implements Payable
{
    public function pay(int $amount): string
    {
        return $amount . '円をクレジットカードで支払いました';
    }
}

class BankTransferPayment implements Payable
{
    public function pay(int $amount): string
    {
        return $amount . '円を銀行振込で支払いました';
    }
}
?>

Payable を実装したクラスは、必ず pay() メソッドを持つことをPHPが保証してくれます。

もし pay() メソッドを実装し忘れると、Fatal error: Class must implement method pay() というエラーが出て、実行前に気づけます。

継承とインターフェースの使い分け

比較表で整理する

継承とインターフェースは似ているようで、目的がまったく違います。

観点継承(extendsインターフェース(implements
引き継げるものプロパティ・メソッドの実装メソッドの「約束」のみ(実装なし)
継承できる数1つの親クラスのみ複数のインターフェースを実装可能
主な目的共通の処理をまとめて再利用する異なるクラスに共通の「振る舞い」を保証する
使う場面の例犬・猫が「動物」の性質を引き継ぐクレジットカード決済・銀行振込決済がどちらも「支払える」ことを保証する

複数の支払い方法を1つの処理でまとめて扱う

インターフェースの真価は、異なるクラスを「同じ約束を守るもの」として1つの処理でまとめて扱えることにあります。

<?php
function processPayment(Payable $payment, int $amount): string
{
    // $paymentがCreditCardPaymentでもBankTransferPaymentでも、
    // pay()メソッドを持っていることが保証されているので同じように呼べる
    return $payment->pay($amount);
}

echo processPayment(new CreditCardPayment(), 3000);
echo processPayment(new BankTransferPayment(), 5000);
?>

processPayment() は「Payable を実装しているクラスなら何でも受け取れる」という書き方になっており、支払い方法が増えても関数側は修正不要です。

私がつまずいたポイント:継承の使いすぎ

継承を覚えたての頃、私は「共通する部分が少しでもあれば継承でまとめよう」と考えて、Employee(社員)クラスを継承して Manager(マネージャー)クラス、さらにそれを継承して Director(役員)クラスを作る、という階層をどんどん深くしていったことがあります。

最初は良かったのですが、後から「マネージャーだけ給与計算のルールを変えたい」という仕様変更が入ったとき、どの階層のどのメソッドを直せばいいのか自分でも分からなくなってしまいました。

継承の階層は深くなるほど、変更の影響範囲が追いにくくなります。

このとき先輩から「継承は『AはBの一種である』と言い切れる関係だけに使うべき。『できることが共通しているだけ』ならインターフェースの方が安全」とアドバイスをもらい、実際にインターフェース中心の設計に直したところ、コードの見通しがぐっと良くなりました。

「継承かインターフェースか迷ったら、まずインターフェースを検討する」というのは、今でも意識しているルールです。

Laravelとの接点

Laravelでは、Illuminate\Contracts という名前空間の中に、多数のインターフェースが用意されています。

たとえばキャッシュ機能を扱うクラスは Illuminate\Contracts\Cache\Repository インターフェースを実装しており、これによって「キャッシュの保存先がファイルでもRedisでも、同じメソッドで扱える」という柔軟さが実現されています。

今回学んだ「インターフェースは振る舞いの約束」という考え方は、Laravelのこうした設計を理解するうえでの土台になります。

まとめ

この記事のポイント

  • 継承(extends)は、親クラスのプロパティ・メソッドをそのまま引き継ぐ仕組み
  • 子クラスで同名のメソッドを定義すると上書き(オーバーライド)できる。parent:: で親の処理も活かせる
  • インターフェース(implements)は、メソッドの「実装」ではなく「約束」だけを定義する
  • 継承は1つのクラスしか継承できないが、インターフェースは複数実装できる
  • 「AはBの一種」と言い切れる関係には継承、「できることが共通」なだけならインターフェースを選ぶ

次に読むべき記事

→ 次の記事:PHPのアクセス修飾子(public/protected/private)の使い分け

タイトルとURLをコピーしました