PHP Interfaces

Define contracts that classes must fulfill. Master implements, multiple interfaces, built-in interfaces like Countable and Iterator, and when to choose interface over abstract.

Intermediate 8 min read 8 examples

What is an Interface?

An interface is a contract: a list of method signatures that an implementing class must provide. It has no method bodies and no state - just the shape.

Defining an Interface

PHPLogger.php
<?php
interface Logger {
    public function info(string $message): void;
    public function error(string $message, ?Throwable $e = null): void;
    public function debug(string $message): void;
}

All methods are implicitly public. You cannot declare properties or non-static method bodies.

Implementing an Interface

PHP
<?php
class FileLogger implements Logger {
    public function __construct(private string $path) {}

    public function info(string $message): void {
        $this->write("INFO", $message);
    }

    public function error(string $message, ?Throwable $e = null): void {
        $this->write("ERROR", $message . ($e ? " | " . $e->getMessage() : ""));
    }

    public function debug(string $message): void {
        $this->write("DEBUG", $message);
    }

    private function write(string $level, string $msg): void {
        file_put_contents($this->path, "[$level] $msg\n", FILE_APPEND);
    }
}

class ConsoleLogger implements Logger {
    public function info(string $message): void  { echo "INFO: $message\n"; }
    public function error(string $message, ?Throwable $e = null): void { echo "ERR: $message\n"; }
    public function debug(string $message): void { echo "DBG: $message\n"; }
}

Multiple Interfaces

Unlike inheritance, a class can implement many interfaces:

PHP
<?php
interface Cacheable {
    public function cacheKey(): string;
}

interface Serializable {
    public function toArray(): array;
}

class Product implements Cacheable, Serializable {
    public function __construct(
        public int $id,
        public string $name,
        public float $price,
    ) {}

    public function cacheKey(): string {
        return "product_{$this->id}";
    }

    public function toArray(): array {
        return ["id" => $this->id, "name" => $this->name, "price" => $this->price];
    }
}

Interfaces Extending Interfaces

PHP
<?php
interface Readable {
    public function read(): string;
}

interface Writable {
    public function write(string $data): int;
}

// Interface inheritance - composite contract
interface ReadWrite extends Readable, Writable {
    public function close(): void;
}

class MemoryStream implements ReadWrite {
    public function read(): string { /* ... */ }
    public function write(string $data): int { /* ... */ }
    public function close(): void { /* ... */ }
}

Interface Constants

PHP
<?php
interface HttpStatus {
    const OK         = 200;
    const NOT_FOUND  = 404;
    const SERVER_ERR = 500;
}

class Response implements HttpStatus {
    public int $status = self::OK;
}

echo HttpStatus::OK;            // 200
echo Response::NOT_FOUND;       // 404 (inherited)

Type-Hinting Interfaces

The real power - code against the contract, not the implementation:

PHP
<?php
class OrderProcessor {
    public function __construct(private Logger $logger) {}

    public function process(Order $order): void {
        $this->logger->info("Processing #{$order->id}");
        // ...
    }
}

// Swap implementations at the boundary
$processor = new OrderProcessor(new FileLogger("orders.log"));
// or
$processor = new OrderProcessor(new ConsoleLogger());

// In tests, inject a fake
$processor = new OrderProcessor(new InMemoryLogger());

Built-in PHP Interfaces

PHP ships with interfaces you can implement to plug into language features:

InterfaceEnables
Countablecount($obj)
Iterator / IteratorAggregateforeach ($obj as ...)
ArrayAccess$obj["key"] syntax on objects
Stringable(string) $obj via __toString()
JsonSerializablejson_encode($obj) control
ThrowableAnything that can be thrown
PHP
<?php
class Cart implements Countable, IteratorAggregate, JsonSerializable {
    private array $items = [];

    public function add(string $sku): void { $this->items[] = $sku; }

    public function count(): int {
        return count($this->items);
    }

    public function getIterator(): Iterator {
        return new ArrayIterator($this->items);
    }

    public function jsonSerialize(): array {
        return ["items" => $this->items, "count" => count($this)];
    }
}

$c = new Cart();
$c->add("A"); $c->add("B");

echo count($c);                  // 2 - Countable
foreach ($c as $sku) echo $sku;  // AB - Iterator
echo json_encode($c);            // {"items":["A","B"],"count":2}

Interface vs Abstract Class

AspectInterfaceAbstract Class
Method bodiesNoYes (concrete methods OK)
PropertiesNoYes
ConstructorNoYes
How many can a class use?Many (implements A, B, C)Only one (extends X)
ConstantsYesYes
Use when...You need a contract with no shared codeSubclasses share state or implementation

Next Steps

Frequently Asked Questions

Interfaces enable multiple contracts - a class can implement many interfaces but extend only one class. They also separate what a class does from how, making mocking and testing easier.

No. Interfaces are pure contracts - method signatures only. For shared default implementations, use an abstract class or a trait.

Yes. All interface methods are implicitly public. Private or protected makes no sense - the whole point is exposing a contract.

Yes, as long as the signatures are compatible. The single implementation satisfies both interfaces.

No - that's over-engineering. Extract an interface only when you have (or will have) multiple implementations, or when you need to mock it in tests. Concrete domain classes used in exactly one place don't need one.