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. 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.
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.
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.
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.
O próximo exemplo irá mostrar o comportamento da contravariância.
Variância de propriedade
Por padrão, as propriedades não são covariantes nem contravariantes e, portanto, invariantes.
Ou seja, o tipo delas não pode mudar em nenhuma classe filha.
A razão para isso é que as operações "get" devem ser covariantes
e as operações "set" devem ser contravariantes.
A única maneira de uma propriedade satisfazer ambos os requisitos é ser invariante.
A partir do PHP 8.4.0, com a adição de propriedades abstratas (em uma interface ou classe abstrata) e
propriedades virtuais,
é possível declarar uma propriedade que possui apenas uma operação get ou set.
Como resultado, propriedades abstratas ou propriedades virtuais que exigem apenas uma operação "get" podem ser covariantes.
Da mesma forma, uma propriedade abstrata ou propriedade virtual que requerem apenas uma operação "set" pode ser contravariante.
Uma vez que uma propriedade tenha uma operação get e set, entretanto,
ela não será mais covariante ou contravariante para extensão adicional.
Ou seja, agora é invariante.
Exemplo #1 Variância do tipo propriedade
<?php
class Animal {}
class Dog extends Animal {}
class Poodle extends Dog {}
interface PetOwner
{
// Apenas uma operação get é necessária, portanto isso pode ser covariante.
public Animal $pet { get; }
}
class DogOwner implements PetOwner
{
// Este pode ser um tipo mais restritivo, já que o lado "get"
// ainda retorna um Animal. Porém, como propriedade nativa,
// os filhos desta classe não podem mais alterar o tipo.
public Dog $pet;
}
class PoodleOwner extends DogOwner
{
// Isso NÃO É PERMITIDO, porque DogOwner::$pet tem operações
// get e set definidas e obrigatórias.
public Poodle $pet;
}
?>