Open/Closed Principle
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints note that automated detection is 'no' and that phpstan/psalm can flag structural patterns (e.g. growing switch/if-type chains), but these tools cannot reliably detect OCP violations as a general principle — they can only catch specific code patterns. Most violations require human code review to recognise that a method or class is being modified repeatedly for the same reason rather than extended.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes replacing switch/if-else chains with polymorphism — introducing interfaces, creating new classes per type, and wiring them together. This is more than a one-line patch; it typically involves extracting an interface, creating multiple concrete implementations, and updating call sites, touching multiple files within a component but not necessarily a cross-cutting architectural rework.
Closest to 'persistent productivity tax' (b5). OCP applies across all contexts (web, cli, queue-worker) and its misapplication — either not applying it (growing switch chains) or over-applying it (premature abstraction) — slows down many work streams over time. It is not as load-bearing as shared mutable state or a global auth helper (b7), but its persistent influence on how new types and behaviours are added makes it a meaningful ongoing tax.
Closest to 'notable trap — a documented gotcha most devs eventually learn' (t5). The misconception field directly identifies the canonical wrong belief: developers think OCP means creating interfaces for every class upfront, when it is actually a reactive principle applied only after observed change. This is a well-documented gotcha (over-engineering via premature abstraction vs. under-engineering with growing switch chains) that most developers eventually learn but routinely get wrong initially.
Also Known As
TL;DR
Explanation
The O in SOLID, OCP states that you should be able to extend a class's behaviour without modifying its source code. In practice: use interfaces and polymorphism so new behaviour is added by writing a new class, not editing an existing one. A switch statement on a type field that grows every time a new type is added is a classic OCP violation — replace it with a polymorphic hierarchy. OCP reduces the risk that adding new features breaks existing tested behaviour.
Common Misconception
Why It Matters
Common Mistakes
- Adding a new case to a switch statement every time a new type is introduced — add a new class instead.
- Modifying a method's internals to handle a new special case rather than using polymorphism.
- Closing too early — abstracting before you know the variation points leads to wrong abstractions.
- Not using interfaces as the seam for extension — concrete classes are harder to extend without modification.
Code Examples
// Must edit this class every time a new payment provider is added
class PaymentService {
public function charge(string $provider, int $amount): void {
if ($provider === 'stripe') { /* Stripe SDK */ }
elseif ($provider === 'paypal') { /* PayPal SDK */ }
// Adding Braintree means editing this method
}
}
interface PaymentGateway {
public function charge(int $amount): ChargeResult;
}
class StripeGateway implements PaymentGateway { public function charge(int $a): ChargeResult {} }
class PayPalGateway implements PaymentGateway { public function charge(int $a): ChargeResult {} }
class BraintreeGateway implements PaymentGateway { public function charge(int $a): ChargeResult {} }
class PaymentService {
public function __construct(private PaymentGateway $gateway) {}
public function charge(int $amount): ChargeResult { return $this->gateway->charge($amount); }
// Never changes when a new provider is added
}