Kodokon kodokon.com

Inheritance, interfaces, traits and abstract classes

Choose the right design tool - a contract, an algorithm skeleton or horizontal reuse - by knowing the trade-offs of each.

9 min · 3 questions

Open this lesson in Kodokon

PHP gives you four design tools that partly overlap: concrete inheritance, abstract classes, interfaces and traits. The choice comes down to one question: what do you want to share? A contract: interface. A contract plus an implementation skeleton: abstract class. Code shared between classes with no hierarchical link: trait. Concrete inheritance, for its part, is increasingly hard to justify: it couples tightly, and PHP allows only a single parent.

PHP
<?php

declare(strict_types=1);

abstract class Exporter
{
    public function export(array $rows): string
    {
        return $this->head() . $this->body($rows);
    }

    abstract protected function head(): string;

    abstract protected function body(
        array $rows
    ): string;
}

final class CsvExporter extends Exporter
{
    protected function head(): string
    {
        return "id;name\n";
    }

    protected function body(array $rows): string
    {
        $lines = array_map(
            fn (array $r): string => implode(';', $r),
            $rows,
        );

        return implode("\n", $lines);
    }
}

$csv = new CsvExporter();
echo $csv->export([[1, 'Ada'], [2, 'Linus']]);
Template method: the parent fixes the algorithm, the children supply the details.

The interface is the pure contract: signatures and constants, no state and no implementation. Its decisive advantage over the abstract class: a class can implement several of them, and any existing class can adopt the contract after the fact. The abstract class, by contrast, carries state and shared code, but consumes your single inheritance slot. In practice: type your parameters with interfaces (Cache, not ArrayCache) so you can swap implementations, in tests as well as in production.

PHP
<?php

declare(strict_types=1);

interface Cache
{
    public function get(string $key): ?string;

    public function set(
        string $key,
        string $value
    ): void;
}

final class ArrayCache implements Cache
{
    private array $items = [];

    public function get(string $key): ?string
    {
        return $this->items[$key] ?? null;
    }

    public function set(
        string $key,
        string $value
    ): void {
        $this->items[$key] = $value;
    }
}

$cache = new ArrayCache();
$cache->set('lang', 'PHP');
echo $cache->get('lang');
The client code depends on Cache: the implementation stays interchangeable.

Traits solve horizontal sharing: injecting the same methods into classes with no common ancestor. When two traits provide a method with the same name, PHP refuses to choose for you and demands an explicit resolution: insteadof designates the version to keep, as creates an alias to keep the other one accessible. It's verbose by design - the conflict must stay visible in the code.

PHP
<?php

declare(strict_types=1);

trait FileLogger
{
    public function log(string $message): void
    {
        echo "[file] $message\n";
    }
}

trait AuditLogger
{
    public function log(string $message): void
    {
        echo "[audit] $message\n";
    }
}

final class PaymentService
{
    use FileLogger, AuditLogger {
        FileLogger::log insteadof AuditLogger;
        AuditLogger::log as audit;
    }
}

$service = new PaymentService();
$service->log('Payment received');
$service->audit('Payment received');
Trait conflict: insteadof decides, as keeps an alias.

Knowledge check

Make sure you remember the key points of this lesson.

  1. What decisive advantage does the interface have over the abstract class?
    • It can hold shared properties
    • A class can implement several interfaces, but inherit from only one class
    • Its methods run faster
  2. Two traits used by the same class each declare a log() method. What happens?
    • PHP uses the method from the first listed trait
    • Fatal error, unless resolved explicitly with insteadof
    • PHP merges the two methods into one
  3. What can an abstract class contain that an interface cannot?
    • Constants
    • Properties and implemented methods
    • Public method signatures