Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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.14

An 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 classInterface
Ready-made methodsYesNo
PropertiesYesNo
How many can be namedOne parentAny number
When to chooseChildren share codeOnly 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-1

Writing 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());    // Model

This 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.

Thanks for reading!

Donate to the site — we work hard for you.