SOLID原則の実践
5つのSOLID原則を実際のPHPクラスに適用します。
「SOLID原則の実践」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。
SOLIDを採用する理由
SOLIDは、オブジェクト指向PHPを柔軟でテストしやすく、劣化しにくいものに保つ5つの設計原則です。盲目的に従うべき規則ではなく、結合度を下げ、責務を明確にするためのヒューリスティックです。このレッスンでは、実際にリリースするPHPクラスに各原則を適用します。
- Single Responsibility
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
単一責任
クラスには変更する理由が1つだけあるべきです。レポートを作成し、HTMLとして整形し、メールで送信するクラスには、変更する理由が3つあります。永続化、整形、送信処理を分割します。
以下では、Invoiceはデータだけをモデル化し、レンダリングと保存は別の場所で行います。
<?php
final class Invoice {
public function __construct(
public readonly string $number,
public readonly int $cents
) {}
public function total(): float { return $this->cents / 100; }
}
final class InvoiceRenderer {
public function toText(Invoice $i): string {
return sprintf('Invoice %s: $%.2f', $i->number, $i->total());
}
}
$i = new Invoice('INV-1', 12500);
echo (new InvoiceRenderer())->toText($i), PHP_EOL;
SRP:コードの臭いを見抜く
典型的なSRP違反は、クラス名にandが含まれるクラスや、何でも処理するManagerです。関係のないビジネス上の理由で変更されるメソッドに注意します。税ルールの変更とPDFレイアウトの変更の両方で同じクラスに手を入れる必要があるなら、そのクラスには責務が多すぎます。
開放閉鎖原則
ソフトウェアの要素は、拡張に対して開いており、変更に対して閉じているべきです。新しい割引種別を追加する際に、巨大なswitchを編集する必要があってはいけません。ポリモーフィズムを使用し、それぞれのルールを共通インターフェースを実装する独自のクラスにします。
<?php
interface Discount { public function apply(float $total): float; }
final class PercentOff implements Discount {
public function __construct(private float $pct) {}
public function apply(float $t): float { return $t * (1 - $this->pct / 100); }
}
final class FlatOff implements Discount {
public function __construct(private float $amount) {}
public function apply(float $t): float { return max(0, $t - $this->amount); }
}
function checkout(float $total, Discount ...$discounts): float {
foreach ($discounts as $d) { $total = $d->apply($total); }
return $total;
}
echo checkout(100, new PercentOff(10), new FlatOff(5)), PHP_EOL; // 85
リスコフの置換原則
派生型は、基底型が想定されているあらゆる場所で、呼び出し側に予期しない動作を起こすことなく使用できなければなりません。有名な例として、Square extends RectangleはLSPに反します。幅を設定したときに、高さまで暗黙に変わってはいけないためです。共通のShapeインターフェースの背後に別々の型としてモデル化することを優先します。
<?php
interface Shape { public function area(): float; }
final class Rectangle implements Shape {
public function __construct(private float $w, private float $h) {}
public function area(): float { return $this->w * $this->h; }
}
final class Square implements Shape {
public function __construct(private float $side) {}
public function area(): float { return $this->side ** 2; }
}
$shapes = [new Rectangle(2, 3), new Square(4)];
foreach ($shapes as $s) { echo $s->area(), PHP_EOL; }
LSPと契約
LSPはメソッドの契約にも制約を加えます。派生型は事前条件を弱め、事後条件を強めることはできますが、その逆はできません。基底型で宣言されていない新しい例外型を投げたり、基底型がオブジェクトを保証している箇所でnullを返したりすると、置換可能性に違反します。PHPの共変戻り値型と反変パラメーター型は、この一部を型レベルで強制します。
インターフェース分離原則
クライアントは、使用しないメソッドに依存すべきではありません。work()とeat()を持つ肥大化したWorkerインターフェースでは、RobotWorkerにeat()のスタブ実装を強いることになります。役割ごとに焦点を絞ったインターフェースへ分割し、各実装クラスが実際に行うことだけを保証するようにします。
<?php
interface Workable { public function work(): string; }
interface Feedable { public function eat(): string; }
final class Human implements Workable, Feedable {
public function work(): string { return 'coding'; }
public function eat(): string { return 'lunch'; }
}
final class Robot implements Workable {
public function work(): string { return 'welding'; }
}
foreach ([new Human(), new Robot()] as $w) { echo $w->work(), PHP_EOL; }
依存性逆転原則
高レベルの方針は、具体的な詳細ではなく抽象に依存すべきです。ハードコードしたクラスではなく、インターフェースを注入します。これにより、利用側に手を加えずに、テストでは実際のメーラーをフェイクに置き換え、本番環境では別のベンダーに切り替えられます。
<?php
interface Mailer { public function send(string $to, string $body): void; }
final class SmtpMailer implements Mailer {
public function send(string $to, string $body): void {
echo "SMTP -> $to: $body" . PHP_EOL;
}
}
final class SignupService {
public function __construct(private Mailer $mailer) {}
public function register(string $email): void {
$this->mailer->send($email, 'Welcome!');
}
}
(new SignupService(new SmtpMailer()))->register('a@b.com');
DIPとコンテナ
依存性の逆転は原則であり、依存性の注入はそれを満たすための1つの手法です。DIコンテナ(PSR-11)は、コンポジションルートで具体的なSmtpMailerをMailer抽象に結び付けます。利用側がnew SmtpMailer()と記述することはないため、ソースコード上の依存関係の方向は詳細ではなく抽象に向かいます。
<?php
// Composition root wiring (pseudo-container)
$bindings = [
Mailer::class => fn() => new SmtpMailer(),
];
$resolve = fn(string $id) => $bindings[$id]();
$service = new SignupService($resolve(Mailer::class));
$service->register('user@example.com');
SOLIDを一体として使う
これらの原則は互いに補強し合います。ISPによってインターフェースが小さく保たれるため、DIPによる注入も焦点が絞られます。OCPは、LSPによって安全に保たれるポリモーフィズムに依存します。SRPによって、これらすべてを可能にする小さなクラスが得られます。内部は高凝集に、クラス間は疎結合にすることを目指します。
- 一度しか使わないクラスを過剰に抽象化しないでください。
- 2つ目の実装やテストダブルが必要になった時点で、インターフェースを導入します。
実用主義
SOLIDは目的ではなく手段です。実装が1つしかない段階で早まってインターフェースを作ると、メリットがないまま間接層が増えます(「推測による汎用化」)。実際に変更が発生したとき、または変更が明らかに近いときに原則を適用します。テストがあればSOLIDに向けたリファクタリングは容易ですが、初日から盲目的にSOLID化するのは無駄です。
確認問題
SquareとRectangleの継承問題は、どの原則に違反しますか?
振り返り
実際のPHPに5つすべてのSOLID原則を適用しました。
- SRP:クラスごとに変更する理由を1つにします。
- OCP:switchを編集するのではなく、新しいポリモーフィックなクラスで拡張します。
- LSP:派生型が基底型の契約を守ります。
- ISP:肥大化したインターフェースではなく、小さな役割別インターフェースを使います。
- DIP:抽象に依存し、コンポジションルートで詳細を注入します。
儀式的に適用するのではなく、リファクタリングの指針として使用します。
よくある質問
「SOLID原則の実践」レッスンは無料ですか?
はい。「SOLID原則の実践」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。
「SOLID原則の実践」で何を学びますか?
5つのSOLID原則を実際のPHPクラスに適用します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
PHP Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「SOLID原則の実践」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPHP Academyレッスンでコードを書いて実行できますか?
はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- SOLID原則の実践
- 生成パターン:Factory、Builder、Singleton
- 構造パターン:Adapter、Decorator、Facade
- 振る舞いパターン:Strategy、Observer、Command