在 PHP 7.2.0 里,通過(guò)對子類(lèi)方法里參數的類(lèi)型放寬限制,實(shí)現對逆變的部分支持。 自 PHP 7.4.0 起開(kāi)始支持完整的協(xié)變和逆變。
協(xié)變使子類(lèi)比父類(lèi)方法能返回更具體的類(lèi)型; 逆變使子類(lèi)比父類(lèi)方法參數類(lèi)型能接受更模糊的類(lèi)型。
在以下情況下,類(lèi)型聲明被認為更具體:
創(chuàng )建一個(gè)名為 Animal 的簡(jiǎn)單的抽象父類(lèi),用于演示什么是協(xié)變。 兩個(gè)子類(lèi):Cat 和 Dog 擴展(extended)了 Animal。
<?php
abstract 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";
}
}
注意:在這個(gè)例子中,沒(méi)有方法返回了值。 將通過(guò)添加個(gè)別工廠(chǎng)方法,創(chuàng )建并返回 Animal、Cat、Dog 類(lèi)型的新對象。
<?php
interface AnimalShelter
{
public function adopt(string $name): Animal;
}
class CatShelter implements AnimalShelter
{
public function adopt(string $name): Cat // 返回類(lèi)的類(lèi)型不僅限于 Animal,還可以是 Cat 類(lèi)型
{
return new Cat($name);
}
}
class DogShelter implements AnimalShelter
{
public function adopt(string $name): Dog // 返回類(lèi)的類(lèi)型不僅限于 Animal,還可以是 Dog 類(lèi)型
{
return new Dog($name);
}
}
$kitty = (new CatShelter)->adopt("Ricky");
$kitty->speak();
echo "\n";
$doggy = (new DogShelter)->adopt("Mavrick");
$doggy->speak();
以上例程會(huì )輸出:
Ricky meows Mavrick barks
繼續上一個(gè)例子,除了 Animal、 Cat、Dog,我們還添加了 Food、AnimalFood 類(lèi), 同時(shí)為抽象類(lèi) Animal 添加了一個(gè) eat(AnimalFood $food) 方法。
<?php
class 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 類(lèi)重寫(xiě)(overridden)了 eat 方法, 允許傳入任意 Food 類(lèi)型的對象。 而 Cat 類(lèi)保持不變。
<?php
class 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);
以上例程會(huì )輸出:
Ricky eats AnimalFood Mavrick eats Food
但 $kitty 若嘗試 eat $banana 會(huì )發(fā)生什么呢?
$kitty->eat($banana);
以上例程會(huì )輸出:
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given