Wenn du Union-Typen verwendest, musst du die Konzepte Kovarianz und Kontravarianz kennen. Diese werden in der Dokumentation erklärt, aber diese Beispiele können helfen.
Angenommen, wir haben eine Calculator-Klasse, die eine Funktion wie im obigen Beispiel verwendet:
class Calculator
{
public function add(int|float $x, int|float $y): int|float
{
return $x + $y;
}
}
Wenn du die Klasse erweiterst, darfst du die Parametertypen und Rückgabetypen ändern, aber nur nach den folgenden Regeln.
Zuerst dürfen Parametertypen „weiter“ sein, das heißt, sie können mehr Typen akzeptieren als die Basisklasse. Zum Beispiel ist das erlaubt:
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;
}
}
Wir könnten StringCalc anstelle eines ursprünglichen Calculator einsetzen, und alles würde weiterhin funktionieren. Der Aufrufer könnte weiterhin float oder int übergeben, wie ursprünglich, und der Rückgabetyp wäre weiterhin der erwartete int oder float.
Ähnlich kannst du den Rückgabetyp einschränken, das heißt, ihn spezifischer machen, indem du Typen aus der Union entfernst. Dieses Beispiel nutzt diese Regel aus:
class IntCalc extends Calculator
{
public function add(float|int $x, float|int $y) : int
{
return (int)($x + $y);
}
}
Da IntCalc weiterhin int und float akzeptiert, kann es weiterhin ohne Probleme einen ursprünglichen Calculator ersetzen.
Um mehr darüber zu lesen, was erlaubt ist und was nicht, kannst du das Liskovsche Substitutionsprinzip (LSP) heranziehen.
Ein Beispiel dafür, was nicht erlaubt wäre, findest du unten in InvalidCalc:
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
}
}
Die Klasse InvalidCalc lässt sich nicht kompilieren und hat zwei Probleme. Dadurch, dass sie einschränkt, was als Parameter erlaubt ist, könnte InvalidCalc nicht den ursprünglichen Calculator ersetzen, weil sie float nicht mehr akzeptiert. Außerdem könnte sie jetzt einen string zurückgeben, was ebenfalls nicht zu den erlaubten Rückgabetypen der ursprünglichen Klasse gehört.