Коли ми використовуємо обʼєднані типи, слід памʼятати про поняття коваріантності та контраваріантності. Їх пояснено в документації, але ці приклади можуть допомогти.
Припустімо, що ми маємо клас Calculator, який використовує функцію, як у прикладі вище:
class Calculator
{
public function add(int|float $x, int|float $y): int|float
{
return $x + $y;
}
}
Якщо ми розширюємо клас, нам дозволено змінювати типи параметрів і типи повернення, але лише за такими правилами.
По-перше, типи параметрів можуть бути «ширшими», тобто приймати більше типів, ніж базовий клас. Наприклад, це дозволено:
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;
}
}
Ми могли б підставити StringCalc замість оригінального Calculator, і все ще працювало б. Код, який викликає, усе ще міг би передати float або int, як і раніше, а тип повернення все ще був би очікуваним int або float.
Так само можна звузити тип повернення, тобто зробити його конкретнішим, прибравши типи з обʼєднання. У цьому прикладі використано це правило:
class IntCalc extends Calculator
{
public function add(float|int $x, float|int $y) : int
{
return (int)($x + $y);
}
}
Оскільки IntCalc усе ще приймає int і float, він і далі може замінити оригінальний Calculator без проблем.
Щоб дізнатися більше про те, що дозволено, а що ні, можна звернутися до принципу підстановки Лісков (LSP).
Щоб побачити приклад того, що було б заборонено, погляньмо на 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
}
}
Клас InvalidCalc не скомпілюється й має дві проблеми. Обмеживши те, що дозволено як параметр, InvalidCalc не міг би замінити оригінальний Calculator, бо більше не приймає float. Крім того, тепер він міг би повертати string, а це також не входить до дозволених типів повернення оригінального класу.