Refused Bequest
debt(d7/e7/b7/t6)
Closest to 'only careful code review or runtime testing' (d7). detection_hints lists phpstan/psalm but marks automated:no — static analyzers can flag UnsupportedOperationException patterns but the smell itself (semantic mismatch between subclass and parent) requires code review to identify.
Closest to 'cross-cutting refactor across the codebase' (e7). quick_fix says to switch from inheritance to composition — that means every caller relying on the parent type, polymorphic dispatch sites, and type hints across the codebase must change. Not quite architectural rework but well beyond a single-component refactor.
Closest to 'strong gravitational pull' (b7). Inheritance hierarchies shape every subclass and consumer; applies_to spans web/cli/queue, and a wrong parent class forces every derived class and client to work around the unwanted interface. Not b9 because it's typically one hierarchy rather than the whole system shape.
Closest to 'notable trap most devs eventually learn' (t5), nudged to t6 because the misconception is specifically that devs 'fix' it by overriding/nulling methods — the obvious fix is wrong and entrenches the smell. The correct fix (composition) contradicts the inheritance-for-reuse instinct most OOP devs are taught.
Also Known As
TL;DR
Explanation
Refused Bequest is a code smell where a subclass inherits behaviour from a parent but overrides it to do nothing, throws an exception, or simply ignores it. This is a strong signal that the inheritance hierarchy doesn't reflect a genuine is-a relationship. The fix depends on severity: if the subclass reuses some parent behaviour, extract a sibling class from the parent; if it shares no meaningful behaviour with the parent, replace inheritance with composition. The Liskov Substitution Principle is violated whenever a subclass refuses part of its parent's contract.
Common Misconception
Why It Matters
Common Mistakes
- Subclasses that throw UnsupportedOperationException for inherited methods — the interface contract is broken.
- Empty method overrides that silence inherited behaviour — the inheritance is wrong, not the method.
- Using inheritance for code reuse when composition would not impose the unwanted interface.
- Not using interfaces to define capability subsets — implement only what applies rather than inheriting everything.
Code Examples
// Child refuses to use methods inherited from parent
class ReadOnlyList extends ArrayList {
public function add($item): void {
throw new \RuntimeException('Read-only list cannot be modified');
}
public function remove($item): void {
throw new \RuntimeException('Read-only list cannot be modified');
}
}
// Fix: don't inherit what you can't use — use composition or a separate interface
interface ReadableList {
public function get(int $index): mixed;
public function count(): int;
}
class ReadOnlyList implements ReadableList {
public function __construct(private array $items) {}
public function get(int $index): mixed { return $this->items[$index]; }
public function count(): int { return count($this->items); }
// No add/remove — they simply don't exist here
}