Type Widening & Covariance/Contravariance
debt(d2/e3/b3/t7)
Closest to 'caught instantly' (d1) but slightly worse (d2) — PHP itself throws a fatal error for most LSP violations (narrowed params, widened returns), and PHPStan/Psalm catch the subtler variance cases at level 5+.
Closest to 'simple parameterised fix' (e3) — quick_fix says adjust the override to widen params or narrow returns; usually a localised signature change in the subclass, occasionally touching a couple of related classes.
Closest to 'localised tax' (b3) — variance decisions affect class hierarchies and interface contracts in one component; doesn't define system shape but does shape inheritance design.
Closest to 'serious trap' (t7) — misconception field states devs assume strict invariance, and common_mistakes confirms covariance/contravariance directions are routinely confused (return narrows, params widen), contradicting naive 'same type everywhere' intuition.
Also Known As
TL;DR
Explanation
Covariance: subclass return types can be more specific (narrower) — if parent returns Animal, child can return Cat. Contravariance: subclass parameter types can be broader (wider) — if parent accepts Cat, child can accept Animal. PHP 7.4 enforces Liskov Substitution Principle for typed properties (invariant). PHP 8.0+ supports covariant return types. The never return type is the bottom type — covariant in any position. Union types in parameters can widen: parent accepts Cat, child accepts Cat|Dog. This prevents runtime type errors when a subclass is used in place of its parent.
Common Misconception
Why It Matters
Common Mistakes
- Narrowing parameter types in subclasses — child accepts Cat, parent accepts Animal — PHP error.
- Widening return types in subclasses — child returns Animal, parent returns Cat — PHP error.
- Invariant property types — cannot change a typed property's type in a subclass.
- Confusing covariance and contravariance — return types narrow (covariant), parameters widen (contravariant).
Code Examples
// Contravariance violation — narrows parameter incorrectly:
class AnimalShelter {
public function add(Animal $animal): void { }
}
class CatShelter extends AnimalShelter {
public function add(Cat $animal): void { } // Error: cannot narrow parameter type
// If caller passes a Dog expecting AnimalShelter interface, CatShelter breaks
}
// Covariant return types — narrowing is allowed:
interface AnimalRepository {
public function find(int $id): Animal;
}
class CatRepository implements AnimalRepository {
public function find(int $id): Cat { } // Covariant: Cat is more specific than Animal — OK
}
// Contravariant parameters — widening is allowed:
class AnimalShelter {
public function add(Cat $animal): void { }
}
class AllAnimalShelter extends AnimalShelter {
public function add(Animal $animal): void { } // Contravariant: wider — OK
}