共変性と反変性
PHP 7.2.0 で、子クラスのメソッドの引数の型の制限を除く形で、反変性が一部サポートされました。 PHP 7.4.0 以降で、共変性と反変性が完全にサポートされるようになりました。
共変性とは、子クラスのメソッドが、親クラスの返り値よりも、より特定の、狭い型を返すことを許すことです。 一方で、反変性とは、親クラスのものよりも、より抽象的な、広い型を引数に指定することを許すものです。
共変性
共変性がどのように動作するかを示すために、
単純な抽象クラスの親であるAnimal
を作ることにします。
このクラスは子クラス Cat
と Dog
に継承されています。
<?phpabstract class Animal{ protected string $name; public function __construct(string $name) { $this->name = $name; } abstract public function speak();}class Dog extends Animal{ public function speak() { echo $this->name . " barks"; }}class Cat extends Animal { public function speak() { echo $this->name . " meows"; }}
この例では、どのメソッドも値を返さないことに注意して下さい。
以下ではこれらのクラスを使い、
Animal
, Cat
または
Dog
クラスの新しいオブジェクトを返すファクトリをいくつか作ってみることにします。
<?phpinterface AnimalShelter{ public function adopt(string $name): Animal;}class CatShelter implements AnimalShelter{ public function adopt(string $name): Cat // Animal 型を返す代わりに、Cat型を返すことができる { return new Cat($name); }}class DogShelter implements AnimalShelter{ public function adopt(string $name): Dog // Animal 型を返す代わりに、Dog型を返すことができる { return new Dog($name); }}$kitty = (new CatShelter)->adopt("Ricky");$kitty->speak();echo "\n";$doggy = (new DogShelter)->adopt("Mavrick");$doggy->speak();
上の例の出力は以下となります。
Ricky meows Mavrick barks
反変性
既に示した Animal
, Cat
および
Dog
クラスの例を引き続き使い、
Food
と AnimalFood
クラスを追加し、
Animal
抽象クラスに
eat(AnimalFood $food)
メソッドを追加してみましょう。
<?phpclass Food {}class AnimalFood extends Food {}abstract class Animal{ protected string $name; public function __construct(string $name) { $this->name = $name; } public function eat(AnimalFood $food) { echo $this->name . " eats " . get_class($food); }}
反変性 の振る舞いを見るため、Dog
クラスの
eat
メソッドをオーバーライドし、あらゆる
Food
型のオブジェクトを受け入れることにします。
Cat
クラスは変更していません。
<?phpclass Dog extends Animal{ public function eat(Food $food) { echo $this->name . " eats " . get_class($food); }}
さて、反変性がどのように動くかが以下でわかるでしょう。
<?php$kitty = (new CatShelter)->adopt("Ricky");$catFood = new AnimalFood();$kitty->eat($catFood);echo "\n";$doggy = (new DogShelter)->adopt("Mavrick");$banana = new Food();$doggy->eat($banana);
上の例の出力は以下となります。
Ricky eats AnimalFood Mavrick eats Food
しかし、$kitty
の eat
メソッドに
$banana
を渡すとどうなるでしょう?
$kitty->eat($banana);
上の例の出力は以下となります。
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given