Interface vs Abstract Class
debt(d5/e3/b7/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and phpcs as tools — both are specialist static analysis tools, not default linters. The code patterns (abstract class with all abstract methods; interface with default method workarounds) require these tools to flag, not a compiler or basic linter.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates replacing an abstract class with an interface or vice versa, which is a targeted refactor within one component. It's more than a one-line patch — implementors and call sites referencing the type may need updating — but it typically doesn't cross multiple files extensively if the hierarchy is small.
Closest to 'strong gravitational pull' (b7). The choice of interface vs. abstract class shapes the entire class hierarchy and applies across web, cli, and queue-worker contexts. As noted in why_it_matters, it 'shapes the entire class hierarchy' — every implementor, subclass, and consumer is constrained by this decision. Locking implementors into single inheritance via an abstract class is a persistent and pervasive structural commitment.
Closest to 'notable trap' (t5). The misconception is well-documented: developers conflate the two constructs, use abstract classes when interfaces suffice (locking into single inheritance), or define both but only ever use the abstract. These are documented gotchas that most PHP developers eventually learn, but the 'obvious' approach (grabbing an abstract class for shared-looking concerns) is frequently wrong.
Also Known As
TL;DR
Explanation
Choose an interface when defining a capability contract with no shared state — a class can implement multiple interfaces, enabling flexible type composition without inheritance constraints. Choose an abstract class when subclasses genuinely share implementation code or state that would be duplicated otherwise. PHP 8.0 added constructor promotion to abstract classes, making them more concise. A practical approach: start with an interface; introduce an abstract class alongside it only when you find yourself duplicating implementation across multiple implementors. Never use abstract classes solely to prevent instantiation — a private constructor on a final class is cleaner.
Diagram
flowchart LR
subgraph Interface
INT2[interface Printable<br/>print method - no body<br/>all methods public abstract]
INT_USE[Multiple interfaces<br/>implements A B C]
INT_WHEN[When: define a contract<br/>unrelated classes share behaviour]
end
subgraph Abstract_Class
ABS[abstract class Shape<br/>can have concrete methods<br/>can have state properties]
ABS_USE[Single inheritance<br/>extends only one]
ABS_WHEN[When: shared base implementation<br/>related class hierarchy]
end
style INT2 fill:#1f6feb,color:#fff
style ABS fill:#6e40c9,color:#fff
style INT_WHEN fill:#238636,color:#fff
style ABS_WHEN fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Using an abstract class when an interface is sufficient — locks implementors into single inheritance.
- Adding default method implementations to interfaces via abstract classes when PHP traits would be cleaner.
- Defining both an interface and an abstract class implementing it but only ever using the abstract — the interface adds no value if never used independently.
- Not considering that interfaces are more stable contracts — an abstract class's non-abstract methods can break subclasses when changed.
Code Examples
// Abstract class used as interface — unnecessary inheritance constraint:
abstract class Serializable {
abstract public function serialize(): string;
abstract public function unserialize(string $data): void;
// No shared implementation — this should be an interface
}
// Classes that implement this must use their single inheritance slot
// Interface — pure contract, no implementation, multiple allowed
interface Notifiable {
public function notify(string \$message): void;
}
// Abstract class — shared implementation + enforced contract
abstract class BaseNotifier {
abstract protected function send(string \$to, string \$msg): void;
public function notify(string \$message): void {
\$this->log(\$message);
\$this->send(\$this->recipient, \$message);
}
}
// Use interface when: unrelated classes share a contract
// Use abstract when: related classes share implementation
// PHP: implements multiple interfaces, extends one abstract
class EmailNotifier extends BaseNotifier implements Notifiable, Loggable {
protected function send(string \$to, string \$msg): void { /* SMTP */ }
}