PHPのクラスとは何か
クラスは、データを表すプロパティと、そのデータを操作するメソッドをまとめた設計図です。クラスから「インスタンス」と呼ばれる実体を作り、メソッドを呼び出して利用します。PHPでは、クラスを class で定義し、new でインスタンスを生成します。
<?php
class User
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function greet(): string
{
return "こんにちは、{$this->name}さん";
}
}
$user = new User('佐藤');
echo $user->greet();
// 期待される表示:
// こんにちは、佐藤さん$name を private にすると、クラスの外部から直接変更されないようにできます。外部から必要な操作だけを public メソッドとして公開することで、データの扱いをクラス内に集約できます。
継承で共通処理を再利用する
継承では、親クラスの public や protected のプロパティ・メソッドなどを子クラスが引き継ぎます。子クラスは親の処理をそのまま利用でき、必要であればメソッドを上書きして固有の処理を追加できます。
<?php
class Animal
{
public function speak(): string
{
return '音を出します';
}
}
class Dog extends Animal
{
public function speak(): string
{
return 'ワンワン';
}
}
$dog = new Dog();
echo $dog->speak();
// 期待される表示:
// ワンワン継承は共通処理の再利用に向いています。一方で、親クラスと子クラスの関係が強くなるため、親クラスの変更が複数の子クラスへ影響することがあります。単にコードを短くする目的ではなく、「子は親の一種」と説明できる関係かを確認しましょう。
インターフェースで実装の契約を定義する
インターフェースは、クラスが実装すべきメソッドを定める契約です。具体的な処理は持たず、実装クラスが implements で指定します。インターフェースのメソッドを実装しないクラスは、契約を満たせません。
<?php
interface Notifier
{
public function send(string $message): string;
}
class EmailNotifier implements Notifier
{
public function send(string $message): string
{
return "メール送信: {$message}";
}
}
class LogNotifier implements Notifier
{
public function send(string $message): string
{
return "ログ記録: {$message}";
}
}
function notifyUser(Notifier $notifier): string
{
return $notifier->send('注文を受け付けました');
}
echo notifyUser(new EmailNotifier());
// 実装を LogNotifier に差し替えても、
// notifyUser() の処理は変更しません。呼び出し側が Notifier という契約だけに依存すれば、EmailNotifier や LogNotifier のような具体的なクラスを差し替えやすくなります。実装の詳細を分離できるため、テスト用のクラスを用意する場合にも役立ちます。
継承とインターフェースを使い分ける
| 観点 | 継承 | インターフェース |
|---|---|---|
| 共有するもの | 親クラスの処理やデータ | 実装すべきメソッドの契約 |
| 関係性 | 子が親の一種である | 異なるクラスが同じ役割を持つ |
| 実装の自由度 | 親クラスの設計に影響されやすい | 各クラスが自由に処理を実装できる |
| 主な用途 | 共通処理の再利用 | 実装の差し替えや依存関係の分離 |
共通の実装を受け継ぎたい場合は継承を検討します。共通の約束だけを定め、複数の実装を交換できるようにしたい場合はインターフェースが適しています。小さな処理まで無理に親クラスへ集約せず、将来の変更やテストのしやすさを基準に選びましょう。
クラス・継承・インターフェースを組み合わせる
実際の設計では、インターフェースで共通の操作を定義し、必要に応じて親クラスへ共通処理を切り出します。呼び出し側はインターフェース型を受け取ることで、具体的な実装クラスの違いを意識せずに済みます。
<?php
interface ReportExporter
{
public function export(string $title): string;
}
class BaseExporter
{
protected function formatTitle(string $title): string
{
return "[REPORT] {$title}";
}
}
class TextExporter extends BaseExporter implements ReportExporter
{
public function export(string $title): string
{
return $this->formatTitle($title);
}
}
function createReport(ReportExporter $exporter): string
{
return $exporter->export('売上レポート');
}
echo createReport(new TextExporter());
// 期待される表示:
// [REPORT] 売上レポート保守しやすいPHPコードにするための注意点
- 親クラスに責務を詰め込みすぎず、クラスごとの役割を明確にする。
- インターフェースを増やしすぎず、複数の実装で本当に共有したい契約だけを定義する。
- メソッド名や引数の役割を揃え、利用側が実装の違いを意識しなくて済む形を目指す。
- 仕様変更時の影響範囲を確認し、必要なら継承より委譲も検討する。