Cuando usas tipos de unión, debes tener en cuenta los conceptos de covarianza y contravarianza. Estos se explican en la documentación, pero estos ejemplos pueden ayudarte.
Supongamos que tenemos una clase Calculator que usa una función como la del ejemplo anterior:
class Calculator
{
public function add(int|float $x, int|float $y): int|float
{
return $x + $y;
}
}
Si extiendes la clase, puedes cambiar los tipos de parámetro y los tipos de retorno, pero solo según las siguientes reglas.
Primero, los tipos de parámetro pueden ser «más amplios», lo que significa que pueden aceptar más tipos que la clase base. Por ejemplo, esto está 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;
}
}
Podríamos sustituir StringCalc en lugar de un Calculator original y todo seguiría funcionando. Quien llama podría seguir enviando el float o el int que originalmente podía, y el tipo de retorno seguiría siendo el int o el float esperado.
De manera similar, puedes reducir el tipo de retorno, es decir, hacerlo más específico al quitar tipos de la unión. Este ejemplo aprovecha esta regla:
class IntCalc extends Calculator
{
public function add(float|int $x, float|int $y) : int
{
return (int)($x + $y);
}
}
Como IntCalc sigue aceptando int y float, todavía puede sustituir a un Calculator original sin problemas.
Para leer más sobre qué está permitido y qué no, puedes consultar el principio de sustitución de Liskov (LSP).
Para ver un ejemplo de lo que no estaría permitido, echa un vistazo a InvalidCalc a continuación:
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
}
}
La clase InvalidCalc no compilará y tiene dos problemas. Al reducir lo que se permite como parámetro, InvalidCalc no podría ocupar el lugar del Calculator original porque ya no acepta float. Además, ahora podría devolver un string, que tampoco está entre los tipos de retorno permitidos de la clase original.