Quando você usa tipos união, precisa conhecer os conceitos de covariância e contravariância. Eles são explicados na documentação, mas estes exemplos podem ajudar.
Suponha que temos uma classe Calculator que usa uma função como no exemplo acima:
class Calculator
{
public function add(int|float $x, int|float $y): int|float
{
return $x + $y;
}
}
Se você estender a classe, pode alterar os tipos dos parâmetros e o tipo de retorno, mas apenas de acordo com as regras a seguir.
Primeiro, os tipos dos parâmetros podem ser "mais amplos", ou seja, podem aceitar mais tipos do que a classe base. Por exemplo, isto é permitido:
class StringCalc extends Foo
{
public function add(float|int|string $x, float|int|string $y): int|float
{
if (!is_numeric($x) || !is_numeric($y)) {
throw new \InvalidArgumentException('$x and $y must be numeric');
}
return $x + $y;
}
}
Poderíamos substituir StringCalc no lugar de um Calculator original e tudo continuaria funcionando. Quem chama ainda conseguiria enviar o float ou o int que já podia originalmente, e o tipo de retorno ainda seria o int ou float esperado.
Da mesma forma, você pode estreitar o tipo de retorno, ou seja, torná-lo mais específico removendo tipos da união. Este exemplo aproveita essa regra:
class IntCalc extends Calculator
{
public function add(float|int $x, float|int $y) : int
{
return (int)($x + $y);
}
}
Como o IntCalc ainda aceita int e float, ele continua podendo substituir um Calculator original sem problemas.
Para ler mais sobre o que é e o que não é permitido, consulte o Princípio da Substituição de Liskov (LSP).
Para um exemplo do que não seria permitido, dê uma olhada em InvalidCalc abaixo:
class InvalidCalc extends Calculator
{
public function add(int $x, int $y) : int|float|string
{
// Declaration must be compatible with Calculator->add(x: float|int, y: float|int)
// Return type declaration must be compatible with Calculator->add(x: float|int, y: float|int) : float|int
}
}
A classe InvalidCalc não vai compilar e tem dois problemas. Ao reduzir o que é aceito como parâmetro, InvalidCalc não poderia substituir o Calculator original, porque ela não aceita mais float. Além disso, ela agora poderia retornar uma string, que também não está entre os tipos de retorno permitidos da classe original.