Liskov Substitution Principle
debt(d5/e5/b5/t7)
Closest to 'specialist tool catches it' (d5). detection_hints lists phpstan and psalm — these are specialist static analysis tools that can catch some LSP violations (e.g. narrowed parameter types, return type mismatches), but subtle behavioural violations like empty overrides or silent postcondition weakening still require careful code review. Not caught by the compiler or default linters.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to use composition instead if a subclass can't honour the contract. Restructuring an inheritance hierarchy to use composition typically touches the subclass, its callers, and the parent — spanning multiple files — and is not a simple one-line or single-pattern replacement.
Closest to 'persistent productivity tax' (b5). LSP applies across web, cli, and queue-worker contexts. A violated substitution principle in a shared base class can silently corrupt behaviour in many consumers, and the debt compounds as more subclasses or callers are added over time. It's not architectural (b7+) but does impose a meaningful, ongoing tax across multiple workstreams.
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field is explicit: developers commonly believe LSP only requires subclasses to keep method signatures intact. In reality, LSP is about honouring the full behavioural contract — preconditions, postconditions, and invariants. This directly contradicts the natural reading of 'substitution' and how most OOP tutorials introduce inheritance, making it a serious cognitive trap that contradicts common mental models.
Also Known As
TL;DR
Explanation
The L in SOLID, LSP requires that a subclass can replace its parent class in any context without breaking the application. Violations include subclasses that throw exceptions for methods the parent allows, weaken postconditions, or strengthen preconditions. A classic example is a Square extending Rectangle: setting width on a Square also changes height, violating Rectangle's contract. PHP's type system and interface contracts enforce syntactic compatibility, but LSP is a semantic guarantee that requires design discipline.
Common Misconception
Why It Matters
Common Mistakes
- Throwing a NotImplementedException in a subclass method — a subtype that cannot fulfil the contract is not a valid subtype.
- Strengthening preconditions in subclasses: parent accepts any string, child only accepts non-empty — callers that pass '' break.
- Weakening postconditions: parent guarantees non-null return, child may return null — breaks null-safe callers.
- Overriding a method to do nothing (empty body) — silent contract violation.
Code Examples
class Rectangle {
public function setWidth(int $w): void { $this->w = $w; }
public function setHeight(int $h): void { $this->h = $h; }
}
class Square extends Rectangle {
// Violates LSP: setting width forces height too
public function setWidth(int $w): void { $this->w = $this->h = $w; }
public function setHeight(int $h): void { $this->w = $this->h = $h; }
}
interface Shape {
public function area(): float;
}
class Rectangle implements Shape {
public function __construct(private float $w, private float $h) {}
public function area(): float { return $this->w * $this->h; }
}
class Square implements Shape {
public function __construct(private float $side) {}
public function area(): float { return $this->side ** 2; }
}