Abstract Classes and Interfaces

Interfaces and abstract classes describe what objects must be able to do, without saying exactly how. An interface is a pure contract that any class can promise to fulfil; an abstract class is a partially built base class that subclasses complete. They're the foundation of flexible, testable PHP code — and of every framework.

Interfaces

An interface lists public methods (and constants). A class that implements it must provide every one of them:

PHP
<?php

interface Shape
{
    public function area(): float;
    public function name(): string;
}

class Circle implements Shape
{
    public function __construct(private float $radius) {}
    public function area(): float { return M_PI * $this->radius ** 2; }
    public function name(): string { return "circle"; }
}

class Square implements Shape
{
    public function __construct(private float $side) {}
    public function area(): float { return $this->side ** 2; }
    public function name(): string { return "square"; }
}

function describe(Shape $shape): string
{
    return sprintf("%s with area %.2f", $shape->name(), $shape->area());
}

foreach ([new Circle(1), new Square(3)] as $s) {
    echo describe($s), "\n";
}
Output
circle with area 3.14
square with area 9.00

describe() accepts any Shape. It doesn't know or care about circles and squares — add a Triangle tomorrow and it works unchanged.

Several interfaces

A class can extend only one class but implement any number of interfaces, each describing one capability:

PHP
<?php

interface Saveable { public function save(): string; }
interface Printable { public function print(): string; }

class Document implements Saveable, Printable
{
    public function __construct(private string $title) {}
    public function save(): string { return "Saving '{$this->title}'"; }
    public function print(): string { return "Printing '{$this->title}'"; }
}

class Photo implements Printable
{
    public function __construct(private string $file) {}
    public function print(): string { return "Printing photo {$this->file}"; }
}

$queue = [new Document("Q3 plan"), new Photo("beach.jpg")];
foreach ($queue as $item) {
    echo $item->print(), "\n";
}
var_dump($queue[1] instanceof Saveable);
Output
Printing 'Q3 plan'
Printing photo beach.jpg
bool(false)

Programming to an interface

When a class depends on an interface instead of a concrete class, you can swap the implementation without touching it — for instance, a real mailer in production and a fake one in tests:

PHP
<?php

interface Mailer
{
    public function send(string $to, string $subject): bool;
}

class SmtpMailer implements Mailer
{
    public function send(string $to, string $subject): bool
    {
        echo "SMTP: sending '$subject' to $to\n";
        return true;
    }
}

class FakeMailer implements Mailer
{
    public array $sent = [];

    public function send(string $to, string $subject): bool
    {
        $this->sent[] = $to;
        return true;
    }
}

class Registration
{
    public function __construct(private Mailer $mailer) {}

    public function register(string $email): void
    {
        $this->mailer->send($email, "Welcome!");
    }
}

new Registration(new SmtpMailer())->register("ana@example.com");

$fake = new FakeMailer();
new Registration($fake)->register("test@example.com");
echo "Fake mailer recorded: ", implode(", ", $fake->sent), "\n";
Output
SMTP: sending 'Welcome!' to ana@example.com
Fake mailer recorded: test@example.com

This combination — interfaces plus constructor injection — is how frameworks let you replace logging, caching, storage or mail drivers with a configuration change.

Built-in interfaces

PHP's own interfaces plug your classes into language features. Countable makes count() work; IteratorAggregate makes foreach work; Stringable is implemented automatically by any class with __toString(); JsonSerializable controls json_encode:

PHP
<?php

class Playlist implements Countable, IteratorAggregate, JsonSerializable
{
    private array $songs = [];

    public function add(string $song): void { $this->songs[] = $song; }
    public function count(): int { return count($this->songs); }
    public function getIterator(): Iterator { return new ArrayIterator($this->songs); }
    public function jsonSerialize(): array { return ['songs' => $this->songs, 'count' => count($this)]; }
}

$p = new Playlist();
$p->add("Blue in Green");
$p->add("So What");

echo count($p), " songs\n";
foreach ($p as $i => $song) {
    echo $i + 1, ". $song\n";
}
echo json_encode($p), "\n";
Output
2 songs
1. Blue in Green
2. So What
{"songs":["Blue in Green","So What"],"count":2}

Abstract classes

An abstract class can't be instantiated. It can contain normal methods and properties shared by its subclasses, plus abstract methods — signatures without bodies — that every concrete subclass must implement:

PHP
<?php

abstract class Report
{
    public function __construct(protected array $rows) {}

    // the overall algorithm is fixed here...
    public function render(): string
    {
        $lines = [$this->header()];
        foreach ($this->rows as $row) {
            $lines[] = $this->formatRow($row);
        }
        $lines[] = $this->footer();
        return implode("\n", $lines);
    }

    // ...and subclasses fill in the steps
    abstract protected function header(): string;
    abstract protected function formatRow(array $row): string;

    protected function footer(): string
    {
        return "(" . count($this->rows) . " rows)";
    }
}

class CsvReport extends Report
{
    protected function header(): string { return "item,qty"; }
    protected function formatRow(array $row): string { return implode(",", $row); }
}

class TextReport extends Report
{
    protected function header(): string { return "ITEM       QTY"; }
    protected function formatRow(array $row): string { return sprintf("%-10s %3d", ...$row); }
    protected function footer(): string { return "--- end ---"; }
}

$rows = [["Apples", 12], ["Pears", 7]];
echo new CsvReport($rows)->render(), "\n\n";
echo new TextReport($rows)->render(), "\n";
Output
item,qty
Apples,12
Pears,7
(2 rows)

ITEM       QTY
Apples      12
Pears        7
--- end ---

This is the template method pattern: the parent defines the steps, the children provide the details.

PHP
<?php

abstract class Shape
{
    abstract public function area(): float;
}

$s = new Shape();
Output
Fatal error: Uncaught Error: Cannot instantiate abstract class Shape in main.php:8
Stack trace:
#0 {main}
  thrown in main.php on line 8

Interface or abstract class?

InterfaceAbstract class
A class can useManyOne (as its parent)
Contains codeNo (only signatures and constants)Yes, shared methods and properties
DescribesA capability: "can be saved", "can send mail"A family of closely related classes

A common combination is an interface for the contract and an abstract base class that implements it partially, so implementations can either extend the base or implement the interface from scratch.

Try it yourself

Define a Discount interface with an apply method, implement two discounts, and apply them to a price.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Output
25% off     80.00 ->  60.00
15.00 off   80.00 ->  65.00
0% off      80.00 ->  80.00