Covariance et Contravariance

À partir de PHP 7.2.0, la contravariance partielle a été introduite en supprimant les restrictions de type sur les paramètres d'une méthode enfant. À partir de PHP 7.4.0, la covariance et la contravariance complètes ont été ajoutées.

La covariance permet à une méthode enfant de retourner un type plus spécifique que le type de retour de sa méthode parente. En revanche, la contravariance permet à un type de paramètre d'être moins spécifique dans une méthode enfant que dans celui de la méthode parente.

Une déclaration de type est considérée comme plus spécifique dans le cas suivant :

Un type de classe est considéré moins spécifique si l'inverse est vrai.

Covariance

Pour illustrer le fonctionnement de la covariance, une simple classe parente abstraite, Animal est créée. Animal sera étendu par des classes enfants, Cat et 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";
}
}

Notez qu'il n'y a pas de méthodes qui renvoient des valeurs dans cet exemple. Quelques fabriques seront ajoutées et renverront un nouvel objet de classe de type Animal, Cat, ou Dog.

<?php

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

class
CatShelter implements AnimalShelter
{
public function
adopt(string $name): Cat // au lieu de renvoyer le type de classe Animal, il peut renvoyer le type de classe Cat
{
return new
Cat($name);
}
}

class
DogShelter implements AnimalShelter
{
public function
adopt(string $name): Dog // au lieu de renvoyer le type de classe Animal, il peut renvoyer le type de classe Dog
{
return new
Dog($name);
}
}

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

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

L'exemple ci-dessus va afficher :

 Ricky meows Mavrick barks 

Contravariance

En reprenant l'exemple précédent avec les classes Animal, Cat et Dog, deux classes appelées Food et AnimalFood sont incluses, et une méthode eat(AnimalFood $food) est ajoutée à la classe abstraite 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);
}
}

Afin de voir le comportement de la contravariance, la méthode méthode eat est surchargée dans la classe Dog afin d'autoriser n'importe quel objet de type Food. La classe Cat reste inchangée.

<?php

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

L'exemple suivant montre le comportement de la contravariance.

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

L'exemple ci-dessus va afficher :

 Ricky eats AnimalFood Mavrick eats Food 

Mais que se passe-t-il si $kitty essaie de manger (eat()) la banane ($banana) ?

$kitty->eat($banana);

L'exemple ci-dessus va afficher :

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