Abstract Classes
debt(d6/e5/b6/t5)
Closest to 'specialist tool catches it' (d5), +1. PHPStan and Psalm can detect some anti-patterns like abstract classes with no abstract methods, but the deeper misuses — choosing abstract class over interface, deep hierarchies, god base classes, LSP violations — require careful code review and architectural judgment. The tools catch surface-level patterns but not the design-level misuse, pushing this above d5.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says 'prefer interfaces,' but replacing an abstract class with an interface requires modifying every subclass to implement methods that were previously inherited, moving shared code to traits or composition, and updating type hints. This typically spans multiple files within a component. Deep hierarchies (a listed common mistake) make remediation even costlier, but a typical single-level misuse is an e5.
Closest to 'persistent productivity tax' (b5), +1. Abstract classes apply across all PHP contexts (web, cli, queue-worker) and tagged with oop/inheritance/solid. A misused abstract class becomes load-bearing: subclasses depend on it, it shapes how new features are added, and it constrains flexibility. It doesn't quite define the system's shape (b9) but exerts strong gravitational pull on the components that inherit from it, affecting many work streams when the hierarchy grows.
Closest to 'notable trap' (t5). The misconception — 'abstract classes are just interfaces with code' — is a well-documented gotcha that most PHP developers eventually learn but initially get wrong. It leads to choosing abstract classes when interfaces suffice, creating unnecessary coupling and rigidity. It's a genuine conceptual trap but not catastrophic since the difference from interfaces is documented and widely discussed, making it a t5 rather than t7.
Also Known As
TL;DR
Explanation
Abstract classes define a partial implementation for a group of related classes while enforcing specific methods via abstract declarations. They can include properties, constructors, visibility modifiers, and fully implemented methods. Subclasses extend the abstract class and must implement all abstract methods. Abstract classes are best suited for modelling an 'is-a' relationship with shared behaviour, often used in the Template Method pattern where the algorithm structure is fixed but steps are customisable. Unlike interfaces, they support state and implementation, but PHP allows only single inheritance, which limits flexibility.
Common Misconception
Why It Matters
Common Mistakes
- Using abstract classes when only a contract is needed — prefer interfaces.
- Creating deep inheritance hierarchies — increases fragility and reduces flexibility.
- Embedding business logic instead of reusable structure — leads to god base classes.
- Violating Liskov Substitution Principle in subclasses — breaking expected behaviour.
Avoid When
- You only need a contract — use interfaces instead.
- Classes are unrelated but share behaviour — use traits or composition.
- You anticipate needing multiple inheritance — PHP does not support it.
- The base class has no real shared implementation.
- The hierarchy is expected to grow deep or frequently change.
When To Use
- Subclasses share meaningful implementation and structure.
- You need to enforce specific steps while allowing custom behaviour.
- Implementing Template Method pattern.
- Building framework extension points with controlled lifecycle.
- Clear is-a relationship exists between base and subclasses.
Code Examples
// ❌ Concrete base class instead of abstract — allows invalid usage
class Animal {
public function speak(): string {
return ''; // invalid default
}
}
$animal = new Animal(); // meaningless instance
abstract class PaymentGateway {
public function charge(Order $order): ChargeResult {
$this->validate($order);
$result = $this->processPayment($order); // abstract step
$this->logCharge($order, $result);
return $result;
}
abstract protected function processPayment(Order $order): ChargeResult;
protected function validate(Order $order): void {
if ($order->total <= 0) {
throw new \InvalidArgumentException('Order total must be positive');
}
}
private function logCharge(Order $order, ChargeResult $result): void {
// logging implementation
}
}
final class StripeGateway extends PaymentGateway {
protected function processPayment(Order $order): ChargeResult {
// Stripe SDK integration
}
}