Composition Over Inheritance
debt(d7/e5/b7/t5)
Closest to 'only careful code review or runtime testing' (d7). While detection_hints lists phpstan and phpmd, the automated field is 'no' — these tools can flag deep hierarchies but cannot determine if inheritance is conceptually appropriate (is-a vs has-a). Detecting misuse requires human judgment during code review to assess whether the relationship is genuinely hierarchical.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests asking is-a vs has-a before adding a subclass, but once deep inheritance exists, fixing it requires extracting shared behavior into injectable services, updating all subclasses to use composition, and potentially changing how objects are constructed across multiple files.
Closest to 'strong gravitational pull' (b7). Applies to all contexts (web, cli, queue-worker) and tagged as oop/solid/design-pattern. A deep inheritance hierarchy becomes load-bearing — every new feature must work within the hierarchy's constraints, and all descendants are coupled to parent changes. The choice shapes how new functionality is added throughout the system.
Closest to 'notable trap' (t5). The misconception clearly states developers wrongly believe 'composition over inheritance means never using inheritance.' This is a documented gotcha that intermediate developers eventually learn — inheritance is correct for genuine is-a relationships, but the principle's name misleads people into avoiding inheritance entirely or, conversely, ignoring the principle because 'sometimes inheritance is fine.'
Also Known As
TL;DR
Explanation
Inheritance creates tight coupling between parent and child — changes to the parent can break all subclasses, and subclasses inherit all parent behaviour whether they want it or not (see Refused Bequest). Composition (injecting collaborators that implement an interface) gives finer control: you choose exactly which behaviours to include, swap them at runtime, and test them independently. The Gang of Four explicitly recommends favouring composition. In PHP, this means preferring interface injection over extends for varying behaviour, reserving inheritance for genuine is-a relationships.
Common Misconception
Why It Matters
Common Mistakes
- Extending a class just to reuse one or two methods — inject the dependency instead.
- Deep inheritance chains (3+ levels) where a change to a grandparent breaks all descendants.
- Using inheritance to share utility methods — extract a utility class and inject or use it statically.
- Confusing is-a (inheritance appropriate) with has-a (composition appropriate) — a Car has-a Engine, not is-a Engine.
Code Examples
class LoggingMailer extends Mailer {
public function send(Message $m): void {
$this->logger->info('Sending email');
parent::send($m);
}
}
// Now we need LoggingQueuedMailer, LoggingRetryMailer — class explosion
// Compose via Decorator — no inheritance needed
class LoggingMailer implements MailerInterface {
public function __construct(
private MailerInterface $inner,
private LoggerInterface $logger,
) {}
public function send(Message $m): void {
$this->logger->info('Sending email to ' . $m->to);
$this->inner->send($m);
}
}