基礎

PHPのインターフェース|implementsで契約を定義する方法

PHPのインターフェース(interface)は、クラスが実装すべきメソッドの「契約」を定義する仕組みです。インターフェース自体には処理の実装は含まれず、メソッドのシグネチャ(名前・引数・戻り値の型)だけを定義します。

クラスは implements でインターフェースを実装し、定義されたすべてのメソッドを実装する義務を負います。抽象クラスと異なり、1つのクラスが複数のインターフェースを実装できるのが大きな特徴です。

基本的な使い方

PHP
<?php
interface Printable {
    public function toString(): string;
}

interface Calculatable {
    public function calculate(): float;
}

class Invoice implements Printable, Calculatable {
    public function __construct(
        private string $customerName,
        private array $items  // [["name" => ..., "price" => ..., "qty" => ...], ...]
    ) {}

    public function toString(): string {
        $lines = ["請求書: {$this->customerName}"];
        foreach ($this->items as $item) {
            $lines[] = "  {$item['name']} x{$item['qty']} = " . ($item['price'] * $item['qty']) . "円";
        }
        $lines[] = "  合計: {$this->calculate()}円";
        return implode("\n", $lines);
    }

    public function calculate(): float {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item['price'] * $item['qty'];
        }
        return $total;
    }
}

$invoice = new Invoice("田中商事", [
    ["name" => "ノートPC", "price" => 80000, "qty" => 2],
    ["name" => "マウス", "price" => 3000, "qty" => 5],
]);

echo $invoice->toString() . "\n";
実行結果
請求書: 田中商事
  ノートPC x2 = 160000円
  マウス x5 = 15000円
  合計: 175000円

Invoice クラスは PrintableCalculatable の2つのインターフェースを同時に実装しています。すべてのメソッドを実装しなければコンパイルエラーになります。

型としてのインターフェース

インターフェースは型宣言に使えます。異なるクラスでも同じインターフェースを実装していれば、同じ型として扱えます。

PHP
<?php
interface Logger {
    public function log(string $message): void;
}

class ConsoleLogger implements Logger {
    public function log(string $message): void {
        echo "[CONSOLE] {$message}\n";
    }
}

class FileLogger implements Logger {
    public function log(string $message): void {
        echo "[FILE] {$message}(ファイルに書き込み)\n";
    }
}

// インターフェース型で受け取る
function processOrder(string $item, Logger $logger): void {
    $logger->log("注文受付: {$item}");
    $logger->log("在庫確認中...");
    $logger->log("注文確定: {$item}");
}

echo "--- コンソール出力 ---\n";
processOrder("PHP入門書", new ConsoleLogger());

echo "\n--- ファイル出力 ---\n";
processOrder("PHP入門書", new FileLogger());
実行結果
--- コンソール出力 ---
[CONSOLE] 注文受付: PHP入門書
[CONSOLE] 在庫確認中...
[CONSOLE] 注文確定: PHP入門書

--- ファイル出力 ---
[FILE] 注文受付: PHP入門書(ファイルに書き込み)
[FILE] 在庫確認中...(ファイルに書き込み)
[FILE] 注文確定: PHP入門書(ファイルに書き込み)

関数 processOrder()Logger インターフェース型で受け取るため、ConsoleLogger でも FileLogger でも動作します。実装の切り替えが容易です。

インターフェースの継承

PHP
<?php
interface Readable {
    public function read(): string;
}

interface Writable {
    public function write(string $data): void;
}

// インターフェース同士の継承(複数可)
interface ReadWritable extends Readable, Writable {
    public function seek(int $position): void;
}

class MemoryStream implements ReadWritable {
    private string $buffer = "";
    private int $position = 0;

    public function read(): string {
        return substr($this->buffer, $this->position);
    }

    public function write(string $data): void {
        $this->buffer .= $data;
    }

    public function seek(int $position): void {
        $this->position = $position;
    }
}

$stream = new MemoryStream();
$stream->write("Hello, ");
$stream->write("PHP!");
echo $stream->read() . "\n";
$stream->seek(7);
echo $stream->read() . "\n";
実行結果
Hello, PHP!
PHP!

実用的な例

PHP
<?php
interface Repository {
    public function findById(int $id): ?array;
    public function findAll(): array;
    public function save(array $data): bool;
}

class InMemoryUserRepository implements Repository {
    private array $users = [];
    private int $nextId = 1;

    public function findById(int $id): ?array {
        return $this->users[$id] ?? null;
    }

    public function findAll(): array {
        return array_values($this->users);
    }

    public function save(array $data): bool {
        $data["id"] = $this->nextId++;
        $this->users[$data["id"]] = $data;
        return true;
    }
}

$repo = new InMemoryUserRepository();
$repo->save(["name" => "田中", "age" => 30]);
$repo->save(["name" => "佐藤", "age" => 25]);

$user = $repo->findById(1);
echo "ID=1: {$user['name']}\n";

$all = $repo->findAll();
echo "全件: " . count($all) . "件\n";
実行結果
ID=1: 田中
全件: 2件
インターフェースと抽象クラスの使い分け

インターフェース:契約のみ定義。複数実装可能。「何ができるか」を定義する。
抽象クラス:共通処理も提供。単一継承のみ。「何であるか」を定義する。
両方を組み合わせて使うことも多いです。

注意

インターフェースのメソッドは必ず public でなければなりません。また、プロパティは定義できませんが、定数は定義可能です(PHP 8.1以降)。

まとめ

  • interface はメソッドの契約(シグネチャのみ)を定義する
  • implements で実装し、定義されたすべてのメソッドを実装する義務がある
  • 1つのクラスが複数のインターフェースを実装できる
  • 型宣言に使うことで、実装の差し替えが容易な柔軟な設計ができる
  • 抽象クラスとは目的が異なり、組み合わせて使うことも多い