Quando usi i tipi union, devi conoscere i concetti di covarianza e controvarianza. Sono spiegati nella documentazione, ma questi esempi potrebbero aiutarti.
Supponiamo di avere una classe Calculator che usa una funzione come nell'esempio precedente:
class Calculator
{
public function add(int|float $x, int|float $y): int|float
{
return $x + $y;
}
}
Se estendi la classe, puoi modificare i tipi dei parametri e i tipi restituiti, ma solo secondo le regole seguenti.
Primo, i tipi dei parametri possono essere «più ampi», il che significa che possono accettare più tipi rispetto alla classe base. Per esempio, questo è consentito:
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;
}
}
Potremmo sostituire StringCalc al posto di un Calculator originale e tutto funzionerebbe ancora. Chi chiama potrebbe ancora passare il float o l'int che poteva passare in origine, e il tipo restituito sarebbe ancora l'int o il float previsto.
Allo stesso modo, puoi restringere il tipo restituito, cioè renderlo più specifico rimuovendo tipi dall'unione. Questo esempio sfrutta questa regola:
class IntCalc extends Calculator
{
public function add(float|int $x, float|int $y) : int
{
return (int)($x + $y);
}
}
Dato che IntCalc accetta ancora int e float, può ancora sostituire un Calculator originale senza problemi.
Per saperne di più su ciò che è consentito e ciò che non lo è, puoi consultare il Principio di sostituzione di Liskov (LSP).
Per un esempio di ciò che non sarebbe consentito, dai un'occhiata a InvalidCalc qui sotto:
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 classe InvalidCalc non verrà compilata e presenta due problemi. Riducendo ciò che è consentito come parametro, InvalidCalc non potrebbe sostituire il Calculator originale perché non accetta più float. Inoltre, ora potrebbe restituire una string, che non rientra nemmeno tra i tipi restituiti consentiti della classe originale.