Abstract classes, final and static
Sometimes a class exists only as a base for others: "a shape in general" has no area, while a circle and a square do. Such a class is made abstract. And sometimes, on the contrary, you need to forbid changing behaviour in children — that is what final is for.
An abstract class
An abstract class cannot be created with new. It describes shared behaviour and declares methods that children must write:
<?php
abstract class Shape
{
abstract public function area(): float;
public function describe(): string
{
return 'Area: ' . round($this->area(), 2);
}
}
class Circle extends Shape
{
public function __construct(private float $radius) {}
public function area(): float
{
return M_PI * $this->radius ** 2;
}
}
echo (new Circle(1))->describe(); // Area: 3.14An abstract method has no body — only a signature. The describe() method is a regular one: every child gets it ready-made.
An abstract class cannot be created
<?php
abstract class Shape {}
try {
$shape = new Shape();
} catch (Error $e) {
echo $e->getMessage(); // Cannot instantiate abstract class Shape
}Abstract class or interface
| Abstract class | Interface | |
|---|---|---|
| Ready-made methods | Yes | No |
| Properties | Yes | No |
| How many can be named | One parent | Any number |
| When to choose | Children share code | Only a contract of methods is needed |
final
The word final before a method forbids overriding it; before a class it forbids inheriting from the class:
<?php
class Order
{
final public function number(): string
{
return 'ORD-1';
}
}
final class Money
{
public function __construct(public readonly int $amount) {}
}
echo (new Order())->number(); // ORD-1Writing class Bonus extends Money ends with an error before the program even starts.
self and static
Inside a class self means the class where the code is written, and static the class the method was actually called on:
<?php
class Model
{
public static function create(): static
{
return new static();
}
public static function createSelf(): self
{
return new self();
}
}
class User extends Model {}
echo get_class(User::create()), "\n"; // User
echo get_class(User::createSelf()); // ModelThis is called late static binding. Methods that create objects almost always need static.
The template method
An abstract class and final together give a handy technique: the parent fixes the order of steps, and children supply their content:
<?php
abstract class Report
{
final public function render(): string
{
return $this->title() . "\n" . implode("\n", $this->rows());
}
abstract protected function title(): string;
abstract protected function rows(): array;
}
class SalesReport extends Report
{
protected function title(): string
{
return 'Sales';
}
protected function rows(): array
{
return ['Tea: 3', 'Coffee: 5'];
}
}
echo (new SalesReport())->render();A child cannot change render() but defines the title and the rows.
Common mistake
Long inheritance chains — Model → User → Admin → SuperAdmin — are hard to understand and change: an edit in a parent unexpectedly breaks the children. If classes only need a shared set of methods, take an interface; if shared code, a trait or a separate object passed to the constructor. Keep abstract classes for cases where the children really are varieties of the same thing.