エラーの内訳
誰でも経験があるはずです。簡単なアップデートをデプロイし、ページを更新したら、恐怖の「ホワイトスクリーン・オブ・デス」が表示される。エラーログを確認すると、メソッドの互換性に関するfatalエラーが見つかる。これはPHPが継承ルールを厳格に強制しているために発生します。
Fatal error: Declaration of ChildClass::methodName($arg) must be compatible with ParentClass::methodName($arg)
根本原因
継承を法的契約と考えてみてください。親クラスがメソッドは文字列を受け取ると約束している場合、子クラスが突然配列を要求することはできません。もしそうなれば、親クラスを前提に設計されたコードは、子クラスを受け取った際に破綻します。この概念はリスコフの置換原則として知られています。
PHPはこのエラーをコンパイルフェーズ中にスローします。エンジンがサブクラスのメソッドシグネチャが親クラスまたはインターフェースの定義と異なることに気づいた瞬間に発生します。これにより、実行中に後でアプリがクラッシュすることを防ぎます。
よくあるシナリオと解決策
1. 必須パラメータの追加
これが最も頻繁な原因です。サブクラスで追加のデータが必要になり、メソッドに新しいパラメータを追加します。しかし、そのパラメータが親クラスに存在しない場合、「契約」が破られます。
問題のコード:
class FileUploader {
public function upload($file) { /* ... */ }
}
class S3Uploader extends FileUploader {
// $bucketは必須だが親クラスに存在しないためエラーになる
public function upload($file, $bucket) { /* ... */ }
}
解決策: 新しいパラメータにデフォルト値を設定します。オプションにすることで、子クラスのメソッドは親クラスの単一引数の期待に対して互換性を維持できます。
class S3Uploader extends FileUploader {
// デフォルト値(nullや空文字列)を追加することでミスマッチを解消する
public function upload($file, $bucket = 'default-vault') { /* ... */ }
}
2. 型ヒントのミスマッチとnull許容性
PHP 7.1以降、null許容性(?プレフィックスの使用)がこれらのエラーの一般的な原因となっています。親メソッドがnull値を許可しているのに子クラスが許可しない場合、またはその逆の場合、PHPはプロセスを停止します。
問題のコード:
class UserProfile {
public function update(?string $bio) { /* ... */ }
}
class AdminProfile extends UserProfile {
// エラー:子クラスが'?'を削除し、親より厳しくなっている
public function update(string $bio) { /* ... */ }
}
解決策: 型ヒントを完全に一致させます。親クラスが?stringを使用している場合、子クラスもインターフェースを維持するために?stringを使用する必要があります。
3. 戻り値の型の不一致
戻り値の型はPHP 7.0から厳格に強制されるようになりました。親メソッドがintを返すと指定している場合、子クラスはfloatやstringを返すことができません。
問題のコード:
interface CacheProvider {
public function get(string $key): array;
}
class RedisCache implements CacheProvider {
// エラー:戻り値の型がないか、異なる型を返している
public function get(string $key) { return 'data'; }
}
解決策: 子クラスでインターフェースに合わせた戻り値の型を明示的に定義します。
class RedisCache implements CacheProvider {
public function get(string $key): array {
return ['data'];
}
}
モダンPHP:共変性と反変性
PHP 8.0以降を使用している場合、分散(Variance)のおかげでルールが若干柔軟になっています。パラメータにはより汎用的な型(反変性)を、戻り値にはより具体的な型(共変性)を使用できるようになりました。
- **パラメータの反変性:** 親クラスが`string`を受け取る場合、子クラスは`mixed`を受け取ることができます。
- **戻り値の共変性:** 親クラスが汎用的な`BaseModel`を返す場合、子クラスは`User`オブジェクトを返すことができます。
これらの機能が動作しない場合は、php -vで確認してください。これらの改善はバージョン8.0で完全に成熟しました。
修正の確認方法
手動でブラウザを更新することに頼らないでください。以下のツールを使用して、数秒でエラーを検出できます:
- **ターミナルでのLint:** `php -l path/to/file.php`を実行します。コードを実行せずに構文をチェックします。
- **静的解析:** PHPStanやPsalmなどのツールは非常に役立ちます。`vendor/bin/phpstan analyse`を実行すると、1,000以上のファイルにわたるこれらのミスマッチを10秒以内に見つけられます。
- **シグネチャのコピーペースト:** 迷った場合は、親クラスからメソッドの行をコピーして、子クラスに直接貼り付けてください。
予防のためのプロのヒント
- **IDEに任せる:** PhpStormまたはIntelephense拡張機能付きのVS Codeを使用してください。保存する前に、ミスマッチを赤いテキストでハイライトしてくれます。
- **コンストラクタの例外:** `__construct`は特殊なケースであることを覚えておいてください。子クラスでコンストラクタの引数を自由に変更しても、この特定のfatalエラーは発生しません。
- **継承よりコンポジション:** メソッドシグネチャと常に格闘していると感じたら、それはサインです。ニーズに合わないクラスを継承するのではなく、コンポジションの使用を検討する必要があるかもしれません。

