PHPの抽象クラスについて

採用はこちら

PHPの抽象クラスとは、直接インスタンス化できず、継承されることを前提にしたクラスのことです。

通常のクラスは、次のように new してオブジェクトを作れます。

$user = new User();

一方、抽象クラスはそのまま new できません。

abstract class Animal
{
}

$animal = new Animal(); // エラー

抽象クラスは、複数のクラスに共通する処理をまとめたり、子クラスに特定のメソッド実装を強制したりするときに使います。

たとえば、犬・猫・鳥はすべて「動物」です。

しかし、「動物」という概念そのものを直接オブジェクトとして使うよりも、犬や猫などの具体的なクラスとして使うほうが自然です。

そこで、共通する処理を Animal という抽象クラスにまとめ、犬や猫ごとに異なる処理は子クラスで実装します。

抽象クラスは、簡単にいうと、共通部分を親クラスにまとめ、具体的な違いを子クラスに任せるための仕組みです。

目次

抽象クラスを使う目的

抽象クラスは、主に次のような目的で使います。

目的内容
共通処理をまとめる複数の子クラスで共通する処理を親クラスに書ける
実装を強制する子クラスに必ず作ってほしいメソッドを指定できる
設計ルールを明確にする同じ種類のクラスに共通の構造を持たせられる
型として使う抽象クラスを継承した複数のクラスを同じ型として扱える

抽象クラスを使うと、似たようなクラスを整理しやすくなります。

たとえば、決済処理で考えると、クレジットカード決済、銀行振込、コンビニ決済などは処理内容が異なります。

しかし、どれも「支払いを実行する」という共通点があります。

このようなときに、共通する部分を抽象クラスにまとめておくと、コードの重複を減らしながら、クラスごとの違いも表現できます。

抽象クラスの基本構文

PHPでは、abstract キーワードを使って抽象クラスを定義します。

abstract class Animal
{
    public function sleep(): void
    {
        echo '眠ります';
    }
}

クラス名の前に abstract を付けることで、そのクラスは抽象クラスになります。

abstract class クラス名
{
    // プロパティ
    // メソッド
}

抽象クラスは直接インスタンス化できません。

$animal = new Animal(); // エラー

抽象クラスを使うには、子クラスで継承します。

class Dog extends Animal
{
}

そして、子クラスをインスタンス化します。

$dog = new Dog();
$dog->sleep(); // 眠ります

つまり、抽象クラスは「そのまま使うクラス」ではなく、子クラスに継承させて使うクラスです。

抽象メソッドとは

抽象クラスには、抽象メソッドを定義できます。

抽象メソッドとは、メソッド名だけを定義し、具体的な処理を書かないメソッドです。

abstract public function makeSound(): void;

通常のメソッドは、次のように {} の中に処理を書きます。

public function sleep(): void
{
    echo '眠ります';
}

一方、抽象メソッドには処理を書きません。

abstract public function makeSound(): void;

これは、子クラスに対して、

このメソッドは必ず実装してください

というルールを指定している状態です。

抽象メソッドの具体例

動物を表す抽象クラスを例に見てみましょう。

abstract class Animal
{
    public function sleep(): void
    {
        echo '眠ります';
    }

    abstract public function makeSound(): void;
}

この Animal クラスでは、sleep() は共通処理として親クラスに書いています。

一方、makeSound() は動物ごとに違います。

犬なら「ワン」、猫なら「ニャー」、鳥なら「チュン」と鳴くため、親クラスでは具体的な処理を書かず、抽象メソッドにしています。

子クラスでは、必ず makeSound() を実装します。

class Dog extends Animal
{
    public function makeSound(): void
    {
        echo 'ワン';
    }
}

使うときは、子クラスをインスタンス化します。

$dog = new Dog();

$dog->makeSound(); // ワン
$dog->sleep();     // 眠ります

もし子クラスで抽象メソッドを実装しなければ、エラーになります。

class Dog extends Animal
{
    // makeSound() を実装していないためエラー
}

抽象メソッドを使うことで、子クラスで必要な処理の作り忘れを防げます。

抽象クラスの特徴

直接インスタンス化できない

抽象クラスは、そのまま new できません。

abstract class Animal
{
}

$animal = new Animal(); // エラー

必ず子クラスを作り、その子クラスをインスタンス化します。

class Dog extends Animal
{
}

$dog = new Dog(); // OK

抽象クラスは、単体で使う完成品ではなく、継承されることを前提にした基底クラスです。

抽象メソッドを持てる

抽象クラスには、抽象メソッドを定義できます。

abstract class Animal
{
    abstract public function makeSound(): void;
}

抽象メソッドは、子クラスで必ず実装する必要があります。

class Dog extends Animal
{
    public function makeSound(): void
    {
        echo 'ワン';
    }
}

これにより、子クラスに対して必要なメソッドの実装を強制できます。

通常のメソッドも持てる

抽象クラスには、通常のメソッドも書けます。

abstract class Animal
{
    public function sleep(): void
    {
        echo '眠ります';
    }

    abstract public function makeSound(): void;
}

Animal を継承した子クラスでは、親クラスの通常メソッドをそのまま使えます。

class Dog extends Animal
{
    public function makeSound(): void
    {
        echo 'ワン';
    }
}

$dog = new Dog();
$dog->sleep(); // 眠ります

共通処理を抽象クラスに書いておけば、子クラスごとに同じ処理を何度も書く必要がなくなります。

プロパティを持てる

抽象クラスには、通常のクラスと同じようにプロパティを定義できます。

abstract class Animal
{
    protected string $name;

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

    abstract public function makeSound(): void;

    public function getName(): string
    {
        return $this->name;
    }
}

子クラスでは、親クラスの protected プロパティを利用できます。

class Dog extends Animal
{
    public function makeSound(): void
    {
        echo $this->name . 'がワンと鳴きました';
    }
}

使い方は次の通りです。

$dog = new Dog('ポチ');

echo $dog->getName(); // ポチ
$dog->makeSound();    // ポチがワンと鳴きました

コンストラクタを持てる

抽象クラスにも、通常のクラスと同じようにコンストラクタを書けます。

abstract class User
{
    protected string $name;

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

    abstract public function getRole(): string;

    public function getName(): string
    {
        return $this->name;
    }
}

子クラスでは、親クラスのコンストラクタを利用できます。

class AdminUser extends User
{
    public function getRole(): string
    {
        return '管理者';
    }
}
$admin = new AdminUser('山田');

echo $admin->getName(); // 山田
echo $admin->getRole(); // 管理者

子クラスで独自のコンストラクタを定義する場合は、必要に応じて parent::__construct() を呼びます。

class AdminUser extends User
{
    private array $permissions;

    public function __construct(string $name, array $permissions)
    {
        parent::__construct($name);

        $this->permissions = $permissions;
    }

    public function getRole(): string
    {
        return '管理者';
    }
}

抽象クラスの具体例

決済処理を抽象クラスで表す例

抽象クラスは、実務では決済処理のような場面でよく使われます。

ECサイトでは、複数の支払い方法があります。

支払い方法処理内容
クレジットカード決済カード情報を使って決済する
銀行振込振込先情報を案内する
コンビニ決済支払い番号を発行する
QRコード決済外部決済サービスと連携する

支払い方法ごとに処理内容は違いますが、どれも「支払いを実行する」という共通点があります。

このような場合、抽象クラスを使うと設計しやすくなります。

abstract class Payment
{
    protected int $amount;

    public function __construct(int $amount)
    {
        if ($amount <= 0) {
            throw new InvalidArgumentException('金額は1円以上で指定してください。');
        }

        $this->amount = $amount;
    }

    public function getAmount(): int
    {
        return $this->amount;
    }

    abstract public function pay(): string;
}

この Payment クラスでは、金額を保持する処理やバリデーションを共通化しています。

protected int $amount;

public function __construct(int $amount)
{
    if ($amount <= 0) {
        throw new InvalidArgumentException('金額は1円以上で指定してください。');
    }

    $this->amount = $amount;
}

一方、実際の支払い処理は支払い方法ごとに異なるため、pay() は抽象メソッドにしています。

abstract public function pay(): string;

クレジットカード決済クラスの例

クレジットカード決済クラスは次のように作れます。

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

CreditCardPaymentPayment を継承しているため、Payment のコンストラクタや getAmount() を利用できます。

また、親クラスで定義された抽象メソッド pay() を実装しています。

銀行振込クラスの例

銀行振込クラスは次のように作れます。

class BankTransferPayment extends Payment
{
    public function pay(): string
    {
        return $this->amount . '円を銀行振込で支払いました';
    }
}

クレジットカード決済と銀行振込では、支払い方法の処理内容が異なります。

しかし、金額を保持する処理や金額チェックは Payment クラスにまとめられています。

決済クラスの使い方

使う側は、次のように書けます。

$credit = new CreditCardPayment(5000);
echo $credit->pay();

echo PHP_EOL;

$bank = new BankTransferPayment(8000);
echo $bank->pay();

出力例です。

5000円をクレジットカードで支払いました
8000円を銀行振込で支払いました

このように、抽象クラスを使うと、共通する処理は親クラスにまとめ、異なる処理だけを子クラスに書くことができます。

抽象クラスは型としても使える

抽象クラスは、型として指定できます。

たとえば、先ほどの Payment クラスを使って、次のような関数を作れます。

function checkout(Payment $payment): void
{
    echo $payment->pay();
}

この関数は、Payment を継承したクラスなら受け取れます。

$credit = new CreditCardPayment(5000);
$bank = new BankTransferPayment(8000);

checkout($credit);
checkout($bank);

checkout() 側は、具体的な支払い方法がクレジットカードなのか銀行振込なのかを知る必要がありません。

function checkout(Payment $payment): void
{
    echo $payment->pay();
}

Payment 型として受け取り、pay() を呼ぶだけです。

この設計にしておくと、新しい支払い方法を追加しやすくなります。

class PayPayPayment extends Payment
{
    public function pay(): string
    {
        return $this->amount . '円をPayPayで支払いました';
    }
}

新しいクラスを追加しても、checkout() の中身を変更する必要はありません。

$paypay = new PayPayPayment(3000);

checkout($paypay);

これは、実務でも非常に重要な考え方です。

抽象クラスと通常クラスの違い

通常クラスと抽象クラスの違いは、主に次の通りです。

比較項目通常クラス抽象クラス
new できるかできるできない
抽象メソッドを持てるか持てない持てる
通常メソッドを持てるか持てる持てる
プロパティを持てるか持てる持てる
主な用途単体で使う具体的なクラス継承前提の基底クラス
子クラスに実装を強制できるかできないできる

通常クラスの例

通常クラスは、それ自体で使える完成したクラスです。

class User
{
    public function sayHello(): string
    {
        return 'こんにちは';
    }
}

$user = new User();
echo $user->sayHello();

通常クラスは、具体的な処理がすでに完成しており、そのままインスタンス化できます。

抽象クラスの例

一方、抽象クラスは直接使うのではなく、子クラスに継承させるために使います。

abstract class UserBase
{
    abstract public function sayHello(): string;
}
class AdminUser extends UserBase
{
    public function sayHello(): string
    {
        return '管理者です';
    }
}

抽象クラスは、共通する設計やルールを定義するために使います。

抽象クラスとインターフェースの違い

抽象クラスと似た仕組みに、インターフェースがあります。

インターフェースは、クラスに対して「このメソッドを実装してください」という契約を定義するものです。

interface Payable
{
    public function pay(): string;
}

インターフェースを使う場合、クラス側では implements を使います。

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

抽象クラスとインターフェースの違いをまとめると、次のようになります。

比較項目抽象クラスインターフェース
主な役割共通処理を持つ基底クラス実装すべき仕様・契約
使用キーワードextendsimplements
複数指定クラス継承は1つのみ複数実装できる
通常メソッド持てる基本的に実装本体は持たない
プロパティ通常のプロパティを持てる状態管理には基本的に使わない
コンストラクタ持てる実装本体は持たない
向いている場面共通処理や共通状態がある場合機能の実装を保証したい場合

抽象クラスが向いている場面

抽象クラスは、共通処理や共通プロパティを持たせたい場合に向いています。

abstract class Payment
{
    protected int $amount;

    public function __construct(int $amount)
    {
        $this->amount = $amount;
    }

    public function getAmount(): int
    {
        return $this->amount;
    }

    abstract public function pay(): string;
}

支払い処理に共通の金額管理やバリデーションがあるなら、抽象クラスが便利です。

インターフェースが向いている場面

インターフェースは、特定のメソッドを持っていることだけを保証したい場合に向いています。

interface Payable
{
    public function pay(): string;
}

たとえば、「支払いできる」という機能だけを保証したいなら、インターフェースが向いています。

共通処理を持たせたいなら抽象クラス、機能の実装を約束させたいならインターフェース、という考え方をすると整理しやすくなります。

抽象クラスを使うメリット

共通処理をまとめられる

複数のクラスに同じような処理がある場合、抽象クラスにまとめることでコードの重複を減らせます。

たとえば、次のようなコードがあるとします。

class Dog
{
    protected string $name;

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

    public function sleep(): string
    {
        return $this->name . 'が眠ります';
    }
}

class Cat
{
    protected string $name;

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

    public function sleep(): string
    {
        return $this->name . 'が眠ります';
    }
}

DogCat の両方に、同じようなプロパティやメソッドがあります。

これを抽象クラスにまとめると、次のように書けます。

abstract class Animal
{
    protected string $name;

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

    public function sleep(): string
    {
        return $this->name . 'が眠ります';
    }

    abstract public function makeSound(): string;
}

子クラスでは、違う部分だけを書けばよくなります。

class Dog extends Animal
{
    public function makeSound(): string
    {
        return $this->name . 'がワンと鳴きます';
    }
}

class Cat extends Animal
{
    public function makeSound(): string
    {
        return $this->name . 'がニャーと鳴きます';
    }
}

子クラスに実装を強制できる

抽象メソッドを使うと、子クラスに特定のメソッドを必ず実装させられます。

abstract class Notification
{
    abstract public function send(string $message): string;
}

この Notification クラスを継承するクラスは、必ず send() を実装する必要があります。

class EmailNotification extends Notification
{
    public function send(string $message): string
    {
        return 'メール送信: ' . $message;
    }
}

もし send() を実装しなければ、エラーになります。

class EmailNotification extends Notification
{
    // send() を実装していないためエラー
}

これにより、必要な処理の作り忘れを防げます。

処理の流れを固定できる

抽象クラスでは、親クラス側で処理の流れを決めて、一部の具体的な処理だけを子クラスに任せることができます。

たとえば、データ出力処理を考えてみます。

abstract class Exporter
{
    public function export(): string
    {
        $data = $this->fetchData();

        return $this->format($data);
    }

    protected function fetchData(): array
    {
        return [
            ['name' => '山田', 'age' => 30],
            ['name' => '佐藤', 'age' => 25],
        ];
    }

    abstract protected function format(array $data): string;
}

この Exporter クラスでは、出力処理の流れを固定しています。

public function export(): string
{
    $data = $this->fetchData();

    return $this->format($data);
}

ただし、データをどの形式に変換するかは子クラスに任せています。

CSV形式にするクラスです。

class CsvExporter extends Exporter
{
    protected function format(array $data): string
    {
        $lines = [];

        foreach ($data as $row) {
            $lines[] = implode(',', $row);
        }

        return implode(PHP_EOL, $lines);
    }
}

JSON形式にするクラスです。

class JsonExporter extends Exporter
{
    protected function format(array $data): string
    {
        return json_encode($data, JSON_UNESCAPED_UNICODE);
    }
}

使う側は、同じ export() を呼ぶだけです。

$csv = new CsvExporter();
echo $csv->export();

echo PHP_EOL;

$json = new JsonExporter();
echo $json->export();

このように、親クラスで共通の処理手順を定義し、一部だけ子クラスに実装させる設計をテンプレートメソッドパターンと呼びます。

抽象メソッドのアクセス修飾子

抽象メソッドには、主に public または protected を使います。

abstract class Report
{
    abstract public function output(): string;

    abstract protected function createContent(): string;
}

publicの抽象メソッド

public の抽象メソッドは、外部から呼び出すことを想定したメソッドです。

abstract class Payment
{
    abstract public function pay(): string;
}

外部から $payment->pay() のように呼び出したい場合は、public にします。

protectedの抽象メソッド

protected の抽象メソッドは、親クラスや子クラスの内部で使うことを想定したメソッドです。

abstract class Report
{
    public function output(): string
    {
        return $this->createContent();
    }

    abstract protected function createContent(): string;
}

子クラスでは、次のように実装します。

class SalesReport extends Report
{
    protected function createContent(): string
    {
        return '売上レポート';
    }
}

外部からは output() を呼びます。

$report = new SalesReport();

echo $report->output();

createContent() は内部処理用なので、外部から直接呼ぶ必要はありません。

なお、抽象クラスの通常メソッドやプロパティには private も使えます。

ただし、抽象メソッドは子クラスで実装する必要があるため、基本的には public または protected を使います。

子クラスで実装するときの注意点

アクセス修飾子を狭くしない

抽象メソッドを子クラスで実装するときは、親クラスの定義と互換性のある形にする必要があります。

たとえば、親クラスで次のように定義されているとします。

abstract class Animal
{
    abstract public function makeSound(): string;
}

子クラスでは、同じように public で実装します。

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

親クラスで public と定義されているメソッドを、子クラスで protected にすることはできません。

class Dog extends Animal
{
    protected function makeSound(): string
    {
        return 'ワン';
    }
}

これは、アクセス範囲を狭くしているためエラーになります。

基本的には、次のように覚えておくとよいです。

親クラスの定義子クラスで可能な定義
publicpublic
protectedprotected または public

引数や戻り値の型を互換性のある形にする

抽象メソッドを実装するときは、引数や戻り値の型も親クラスと互換性がある必要があります。

abstract class Formatter
{
    abstract public function format(string $text): string;
}

正しい例です。

class HtmlFormatter extends Formatter
{
    public function format(string $text): string
    {
        return '<p>' . htmlspecialchars($text, ENT_QUOTES, 'UTF-8') . '</p>';
    }
}

型の互換性がない形で実装すると、エラーになります。

たとえば、親クラスが string を返すと定義しているのに、子クラスでまったく違う戻り値型にすると問題になります。

抽象メソッドを実装するときは、親クラスの定義と矛盾しない形にすることが重要です。

抽象クラスを使うべき場面

複数のクラスに共通処理がある場合

複数のクラスに共通処理がある場合、抽象クラスが役立ちます。

たとえば、複数の決済クラスで次のような処理が共通している場合です。

  • 金額を保持する
  • 金額を検証する
  • ログを出力する
  • エラー処理を行う

このような場合、抽象クラスに共通処理をまとめると便利です。

abstract class Payment
{
    protected int $amount;

    public function isValidAmount(): bool
    {
        return $this->amount > 0;
    }

    abstract public function pay(): string;
}

共通処理を親クラスにまとめることで、子クラス側のコードをシンプルにできます。

子クラスに必ず実装させたい処理がある場合

すべての通知クラスに send() を実装させたい場合も、抽象クラスを使えます。

abstract class Notification
{
    abstract public function send(string $message): string;
}

メール通知やSMS通知など、具体的な通知方法は子クラスで実装します。

class EmailNotification extends Notification
{
    public function send(string $message): string
    {
        return 'メール送信: ' . $message;
    }
}
class SmsNotification extends Notification
{
    public function send(string $message): string
    {
        return 'SMS送信: ' . $message;
    }
}

抽象メソッドを定義しておくことで、子クラス側で send() を作り忘れることを防げます。

共通の処理手順を固定したい場合

ファイルアップロードやデータ出力のように、処理の流れは同じで、一部だけ異なる場合にも抽象クラスが向いています。

abstract class FileUploader
{
    public function upload(array $file): string
    {
        $this->validate($file);

        $path = $this->getUploadPath();

        return $this->save($file, $path);
    }

    protected function validate(array $file): void
    {
        if (empty($file['name'])) {
            throw new InvalidArgumentException('ファイル名がありません。');
        }
    }

    abstract protected function getUploadPath(): string;

    protected function save(array $file, string $path): string
    {
        return $path . '/' . $file['name'];
    }
}

画像アップロード用のクラスです。

class ImageUploader extends FileUploader
{
    protected function getUploadPath(): string
    {
        return '/uploads/images';
    }
}

PDFアップロード用のクラスです。

class PdfUploader extends FileUploader
{
    protected function getUploadPath(): string
    {
        return '/uploads/pdfs';
    }
}

使い方は次の通りです。

$uploader = new ImageUploader();

echo $uploader->upload([
    'name' => 'sample.jpg',
]);

抽象クラスを使わないほうがよい場面

共通処理がほとんどない場合

共通処理がなく、「このメソッドを実装してほしい」という目的だけであれば、インターフェースのほうが向いていることがあります。

interface Notifiable
{
    public function send(string $message): string;
}

共通のプロパティや共通処理がないなら、抽象クラスではなくインターフェースを検討するとよいです。

抽象クラスは、共通処理を持たせられることが大きな特徴です。

そのため、共通処理がほとんどない場合に無理に抽象クラスを使うと、かえって設計が複雑になることがあります。

継承関係が不自然な場合

抽象クラスは継承を使うため、親子関係が自然である必要があります。

たとえば、これは自然です。

abstract class Animal
{
}

class Dog extends Animal
{
}

犬は動物なので、Dog extends Animal は自然な関係です。

一方、次のような設計は不自然です。

abstract class Database
{
}

class User extends Database
{
}

ユーザーはデータベースではありません。

このような場合は、継承ではなく、クラスの中で別のクラスを使う設計を検討します。

class UserRepository
{
    public function __construct(private Database $database)
    {
    }
}

「AはBである」と言えるなら継承を使いやすいですが、「AはBを使う」という関係なら、継承ではなく組み合わせで設計するほうが自然です。

抽象クラスでよくあるエラー

抽象クラスをnewしてしまう

抽象クラスは直接インスタンス化できません。

abstract class Animal
{
}

$animal = new Animal(); // エラー

子クラスを作ってから使います。

class Dog extends Animal
{
}

$dog = new Dog();

抽象クラスは、あくまで継承されるためのクラスです。

抽象メソッドを実装していない

抽象メソッドを持つ抽象クラスを継承した場合、子クラスでその抽象メソッドを実装する必要があります。

abstract class Animal
{
    abstract public function makeSound(): string;
}

class Dog extends Animal
{
}

Dog クラスで makeSound() を実装していないため、エラーになります。

正しくは次のように実装します。

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

抽象メソッドに処理を書いてしまう

抽象メソッドには処理本体を書けません。

abstract class Animal
{
    abstract public function makeSound(): string
    {
        return 'ワン';
    }
}

これはエラーです。

抽象メソッドは、次のようにセミコロンで終わります。

abstract class Animal
{
    abstract public function makeSound(): string;
}

処理を書きたい場合は、通常メソッドにします。

abstract class Animal
{
    public function makeSound(): string
    {
        return '何かの鳴き声';
    }
}

アクセス修飾子を狭くしてしまう

親クラスの抽象メソッドが public の場合、子クラスでも public にする必要があります。

abstract class Animal
{
    abstract public function makeSound(): string;
}

正しい例です。

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

間違った例です。

class Dog extends Animal
{
    protected function makeSound(): string
    {
        return 'ワン';
    }
}

親クラスよりアクセス範囲を狭くしているため、エラーになります。

実務での抽象クラスの使い方

コントローラーの基底クラス

Webアプリケーションでは、複数のコントローラーに共通する処理を抽象クラスにまとめることがあります。

abstract class BaseController
{
    protected function jsonResponse(array $data): string
    {
        return json_encode($data, JSON_UNESCAPED_UNICODE);
    }

    abstract public function index(): string;
}
class UserController extends BaseController
{
    public function index(): string
    {
        return $this->jsonResponse([
            'name' => '山田',
        ]);
    }
}

BaseController に共通処理をまとめておけば、各コントローラーで同じ処理を繰り返し書かずに済みます。

外部API連携の基底クラス

外部APIとの通信処理では、共通のリクエスト処理を抽象クラスにまとめられます。

abstract class ApiService
{
    protected string $baseUrl;

    public function __construct(string $baseUrl)
    {
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    protected function request(string $endpoint): array
    {
        return [
            'url' => $this->baseUrl . '/' . ltrim($endpoint, '/'),
        ];
    }

    abstract public function fetch(): array;
}
class UserApiService extends ApiService
{
    public function fetch(): array
    {
        return $this->request('/users');
    }
}

APIごとに取得先は異なっても、ベースURLの管理やリクエスト処理は共通化できます。

リポジトリの基底クラス

データベース操作で、共通の取得処理をまとめる場合にも使えます。

abstract class Repository
{
    protected string $table;

    public function find(int $id): array
    {
        return [
            'table' => $this->table,
            'id' => $id,
        ];
    }

    abstract public function getTableName(): string;
}
class UserRepository extends Repository
{
    protected string $table = 'users';

    public function getTableName(): string
    {
        return $this->table;
    }
}

リポジトリごとに対象テーブルは異なっても、find() のような共通処理は親クラスにまとめられます。

抽象クラスを理解するための考え方

抽象クラスは、次のように考えると理解しやすいです。

abstract class 親クラス
{
    // 共通して使うプロパティ

    // 共通して使うメソッド

    // 子クラスに必ず実装させるメソッド
}

たとえば、通知処理で考えてみます。

abstract class Notification
{
    protected string $recipient;

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

    protected function log(string $message): void
    {
        echo '送信ログ: ' . $message . PHP_EOL;
    }

    abstract public function send(string $message): string;
}

メール通知クラスです。

class EmailNotification extends Notification
{
    public function send(string $message): string
    {
        $this->log($message);

        return $this->recipient . ' にメール送信: ' . $message;
    }
}

SMS通知クラスです。

class SmsNotification extends Notification
{
    public function send(string $message): string
    {
        $this->log($message);

        return $this->recipient . ' にSMS送信: ' . $message;
    }
}

この設計では、次のように役割を分けています。

処理定義する場所
宛先を保持する抽象クラス
ログを出す抽象クラス
実際の送信方法子クラス
send() の実装強制抽象クラス

抽象クラスによって、共通部分と個別部分を整理できます。

まとめ

PHPの抽象クラスとは、直接インスタンス化できず、継承されることを前提にした基底クラスです。

抽象クラスを使うと、共通処理を親クラスにまとめながら、子クラスに必要なメソッドの実装を強制できます。

重要なポイントは次の通りです。

ポイント内容
定義方法abstract class クラス名
インスタンス化直接 new できない
継承方法extends を使う
抽象メソッド子クラスで実装する必要がある
通常メソッド抽象クラス内に書ける
プロパティ通常のプロパティを持てる
コンストラクタ定義できる
型指定引数や戻り値の型として使える
主な用途共通処理の集約、実装ルールの強制、処理手順の固定

抽象クラスの本質は、次の一文で表せます。

共通する処理は親クラスに書き、クラスごとに異なる処理は子クラスに実装させる仕組み

基本形は次の通りです。

abstract class Animal
{
    public function sleep(): string
    {
        return '眠ります';
    }

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

抽象クラスは、単にコードを短くするための機能ではありません。

同じ種類のクラスに共通のルールを持たせ、変更に強い設計を作るための仕組みです。

特に、決済処理、通知処理、ファイル出力、API連携、コントローラー、リポジトリなど、複数のクラスに共通処理がありつつ、一部だけ処理が異なる場面で役立ちます。

以上、PHPの抽象クラスについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次