Covariância e Contravariância

No 7.2.0, a contravariância parcial foi introduzida removendo as restrições de tipo nos parâmetros de um método filho. A partir do PHP 7.4.0, foi adicionado suporte a covariância e contravariância completas.

Covariância permite que um método filho retorne um tipo mais específico que o tipo de retorno de seu método pai. Enquanto que a contravariância permite a um parâmetro ter um tipo menos específico em um método filho, em relação ao método pai.

Uma declaração de tipo é considerada mais específica nos seguintes casos:

Uma classe de tipo é considerada menos específica se o oposto for verdadeiro.

Covariância

Para ilustrar como uma variância funciona, uma classe pai abstrata simples, Animal é criada. Animal será estendida a classes filhas, Cat e Dog.

<?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";
}
}

Note que não há nenhum método que retorne valores neste exemplo. Algumas factories serão adicionadas para retornar um novo objeto das classes Animal, Cat or Dog.

<?php

interface AnimalShelter
{
public function
adopt(string $name): Animal;
}

class
CatShelter implements AnimalShelter
{
public function
adopt(string $name): Cat // em vez de retornar o tipo Animal, pode retornar o tipo Cat
{
return new
Cat($name);
}
}

class
DogShelter implements AnimalShelter
{
public function
adopt(string $name): Dog // em vez de retornar o tipo Animal, pode retornar o tipo Dog
{
return new
Dog($name);
}
}

$kitty = (new CatShelter)->adopt("Ricky");
$kitty->speak();
echo
"\n";

$doggy = (new DogShelter)->adopt("Mavrick");
$doggy->speak();

O exemplo acima produzirá:

 Ricky meows Mavrick barks 

Contravariância

Continuando com o exemplo anterior com as classes Animal, Cat e Dog, duas classes chamadas Food e AnimalFood serão incluídas, e um método eat(AnimalFood $food) é adicionado à classe abstrata Animal.

<?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);
}
}

Para ver o comportamento da contravariância, o método eat é substituído na classe Dog para permitir qualquer objeto do tipo Food. A classe Cat permanece inalterada.

<?php

class Dog extends Animal
{
public function
eat(Food $food) {
echo
$this->name . " eats " . get_class($food);
}
}

O próximo exemplo irá mostrar o comportamento da contravariância.

<?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);

O exemplo acima produzirá:

 Ricky eats AnimalFood Mavrick eats Food 

Mas o que acontece se $kitty tentar comer (eat()) a $banana?

$kitty->eat($banana);

O exemplo acima produzirá:

 Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given 
To Top