Open/Closed Principle — PHP Examples
debt(d7/e7/b7/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints explicitly state automated=no, and while phpstan and deptrac are listed, they cannot reliably detect OCP violations — growing switch/if chains and missing extension points require human architectural review to identify. There is no automated rule that flags 'this class should have been extensible.'
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix describes introducing a strategy or decorator pattern, but this typically means extracting an interface, creating multiple implementing classes, updating all call sites, and restructuring any DI or factory logic — changes that span multiple files and often multiple components. Common mistakes include switch statements on type that permeate multiple layers, making remediation cross-cutting by nature.
Closest to 'strong gravitational pull' (b7). OCP is an architectural design principle that applies to web, cli, and queue-worker contexts (all PHP contexts). Violations accumulate as structural debt — each new type added to a switch chain, or each configuration requiring a code change, compounds over time and shapes how every new feature must be added. The choice of whether to enforce extension points affects the entire codebase trajectory.
Closest to 'notable trap' (t5). The misconception field directly states the documented gotcha: developers believe 'open/closed means you should never modify existing code,' when the real meaning is that new behaviour should be addable via extension rather than editing existing logic. This is a well-known misread of the principle that most developers encounter and must consciously unlearn, but it does not fully contradict a similar concept — it is simply misread.
Also Known As
TL;DR
Explanation
The Open/Closed Principle (OCP, the O in SOLID) means adding a new feature should require writing new code, not editing existing code and potentially breaking it. In PHP, OCP is achieved through: interface-based polymorphism (new payment gateways implement PaymentGatewayInterface without changing the PaymentService), strategy pattern (swap sorting algorithms by injecting a different Sorter), decorator pattern (add logging or caching by wrapping an existing service), and event/hook systems (fire an OrderPlaced event; new behaviour is added by registering listeners). The smell indicating OCP violation is a switch/match on a type tag that must be extended every time a new type is added — replace with polymorphism. Avoid premature OCP abstraction — only apply it where extension points are genuinely needed.
Common Misconception
Why It Matters
Common Mistakes
- Switch statements on type that grow with every new type — use polymorphism instead.
- Configuration-driven behaviour that requires code changes to add new configurations — move to data.
- Not designing extension points in libraries — callers need to monkey-patch instead of extending cleanly.
- Misapplying OCP to stable, simple code — premature abstraction for extension is worse than a small switch.
Code Examples
// Must edit this switch every time a new format is added
function export(Report $r, string $format): string {
return match($format) {
'csv' => exportCsv($r),
'pdf' => exportPdf($r),
};
}
interface ReportExporter {
public function export(Report $r): string;
}
// New formats: implement ReportExporter, register — no existing code changes