Abstract Classes
Share real code AND enforce a contract. Abstract classes give the common implementation while leaving specific methods for subclasses to fill in.
An abstract class sits between a plain class and an interface. You cannot instantiate it directly; it exists to be extended. It can hold concrete methods (shared behaviour) alongside abstract methods (signatures the child must implement). That's the template method pattern in one language feature.
Easy example
<?php
abstract class Shape {
abstract public function area(): float;
// shared, concrete behaviour
public function describe(): string {
return 'Area is ' . round($this->area(), 2);
}
}
class Circle extends Shape {
public function __construct(private float $r) {}
public function area(): float { return 3.14159 * $this->r ** 2; }
}
echo (new Circle(2))->describe();Rule of thumb: reach for an interface when you only need a contract; reach for an abstract class when subclasses also share meaningful code.
Example
When to use it
- Create an AbstractRepository with concrete find() and save() logic but leave buildQuery() abstract for each subclass.
- Build an AbstractNotification that handles retry logic and timestamps while leaving the send() method abstract.
- Use an abstract class as a template method pattern to control algorithm steps while delegating specifics to subclasses.
More examples
Abstract class with template method
The template method export() defines the algorithm skeleton; format() is left abstract for each exporter to implement.
<?php
abstract class DataExporter {
final public function export(array $data): string {
$prepared = $this->prepare($data);
return $this->format($prepared);
}
protected function prepare(array $data): array {
return array_filter($data); // shared pre-processing
}
abstract protected function format(array $data): string;
}
class JsonExporter extends DataExporter {
protected function format(array $data): string {
return json_encode($data, JSON_PRETTY_PRINT);
}
}
$exp = new JsonExporter();
echo $exp->export(['name' => 'Alice', 'age' => null]);Shared state in abstract class
The abstract class provides fill() and get() logic; each subclass only specifies which fields are safe to mass-assign.
<?php
abstract class BaseModel {
protected array $attributes = [];
public function fill(array $data): static {
$this->attributes = array_intersect_key($data, array_flip($this->fillable()));
return $this;
}
public function get(string $key): mixed {
return $this->attributes[$key] ?? null;
}
abstract protected function fillable(): array;
}
class UserModel extends BaseModel {
protected function fillable(): array { return ['name', 'email']; }
}
$u = (new UserModel())->fill(['name' => 'Bob', 'password' => 'secret']);
echo $u->get('name'); // Bob
echo $u->get('password'); // null (not fillable)Cannot instantiate abstract class
PHP prevents direct instantiation of abstract classes; only concrete subclasses that implement all abstract methods can be used.
<?php
abstract class Shape {
abstract public function area(): float;
}
try {
$s = new Shape(); // Fatal: Cannot instantiate abstract class
} catch (\Error $e) {
echo $e->getMessage();
}
class Circle extends Shape {
public function __construct(private float $r) {}
public function area(): float { return M_PI * $this->r ** 2; }
}
echo round((new Circle(3))->area(), 2); // 28.27
Discussion