在 PHP 7.2.0 里,通過對(duì)子類方法里參數(shù)的類型放寬限制,實(shí)現(xiàn)對(duì)逆變的部分支持。 自 PHP 7.4.0 起開始支持完整的協(xié)變和逆變。
協(xié)變使子類比父類方法能返回更具體的類型; 逆變使子類比父類方法參數(shù)類型能接受更模糊的類型。
在以下情況下,類型聲明被認(rèn)為更具體:
創(chuàng)建一個(gè)名為 Animal 的簡(jiǎn)單的抽象父類,用于演示什么是協(xié)變。 兩個(gè)子類:Cat 和 Dog 擴(kuò)展(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è)例子中,沒有方法返回了值。 將通過添加個(gè)別工廠方法,創(chuàng)建并返回 Animal、Cat、Dog 類型的新對(duì)象。
<?php
interface 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();
以上例程會(huì)輸出:
Ricky meows Mavrick barks
繼續(xù)上一個(gè)例子,除了 Animal、 Cat、Dog,我們還添加了 Food、AnimalFood 類, 同時(shí)為抽象類 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 類重寫(overridden)了 eat 方法, 允許傳入任意 Food 類型的對(duì)象。 而 Cat 類保持不變。
<?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