← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

Composition Over Inheritance

quality Intermediate
debt(d7/e5/b7/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

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.

e5 Effort Remediation debt — work required to fix once spotted

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.

b7 Burden Structural debt — long-term weight of choosing wrong

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.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

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.'

About DEBT scoring →

Also Known As

favour composition prefer composition has-a over is-a

TL;DR

Favour assembling behaviour from injected collaborator objects rather than inheriting it from a parent class.

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

Composition over inheritance means never using inheritance. Inheritance is correct for genuine is-a relationships (a Dog is an Animal). The principle warns against using inheritance merely to share implementation between unrelated classes.

Why It Matters

Deep inheritance hierarchies create tight coupling between parent and child — composition allows behaviour to be assembled from small focused objects without inheriting unwanted methods or state.

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

✗ Vulnerable
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
✓ Fixed
// 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);
    }
}

Added 15 Mar 2026
Edited 22 Mar 2026
Views 33
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping F 0 pings S 0 pings S 1 ping M 0 pings T 1 ping W 2 pings T 0 pings F 0 pings S 0 pings S 1 ping M 0 pings T 1 ping W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 2 pings M 0 pings T 0 pings W 0 pings T 0 pings F
No pings yet today
No pings yesterday
Amazonbot 9 Perplexity 7 Ahrefs 4 Unknown AI 3 ChatGPT 2 Google 1
crawler 24 crawler_json 1 pre-tracking 1
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: High
⚡ Quick Fix
Before adding a subclass, ask: is this an IS-A relationship or a HAS-A relationship? — if HAS-A, use composition by injecting the dependency instead
📦 Applies To
any web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Deep inheritance hierarchy 4+ levels; subclass overriding most parent methods; extending concrete class not interface
Auto-detectable: ✗ No phpstan phpmd
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: High ✗ Manual fix Fix: High Context: Class Tests: Update

✓ schema.org compliant