0

Interfaces — contracts, multiple interface implementation

Intermediate5 min read·php-07-008
interviewpsrcompare

Concept

Interfaces in PHP are the primary mechanism for dependency inversion and design by contract. A class that implements an interface guarantees it provides all declared methods with compatible signatures. Interfaces define the "what" — abstract classes define the "what with some how."

Multiple interface implementation: A class can implement any number of interfaces, which is PHP's answer to the limitations of single inheritance. This enables cross-cutting concerns to be expressed as interfaces without forcing them into the inheritance hierarchy.

Interface constants: PHP 8.0+ interfaces can declare constants; implementing classes cannot redefine them.

Interface extending interfaces: An interface can extend other interfaces (interface C extends A, B), combining multiple contracts into one.

PSR interfaces: The PHP-FIG defines standard interfaces (PSR-7 for HTTP messages, PSR-11 for containers, PSR-14 for events) that allow interoperability between frameworks. Laravel's core contracts (in Illuminate\Contracts\*) are interfaces that define the framework's internal API — enabling you to swap implementations in the container.

Why programming to interfaces matters in Laravel: Laravel's service container binds concrete implementations to interface abstractions. When you type-hint CacheInterface rather than RedisCache in a constructor, you can swap to FileCache in tests or staging without changing the consuming code.

Code Example

php
<?php
declare(strict_types=1);

// Defining interfaces
interface Hashable
{
    public function hash(): string;
}

interface Equatable
{
    public function equals(static $other): bool;
}

interface Serializable // PHP has a built-in Serializable, this is for example
{
    public function serialize(): string;
    public static function deserialize(string $data): static;
}

// Implementing multiple interfaces
class UserId implements Hashable, Equatable
{
    public function __construct(private readonly int $value) {}

    public function hash(): string
    {
        return hash('sha256', (string) $this->value);
    }

    public function equals(static $other): bool
    {
        return $this->value === $other->value;
    }

    public function getValue(): int { return $this->value; }
}

// Interface extending interfaces
interface FullyIdentifiable extends Hashable, Equatable
{
    public function getId(): int;
}

// Dependency inversion — type-hint the interface, not the concrete class
interface CacheInterface
{
    public function get(string $key): mixed;
    public function set(string $key, mixed $value, int $ttl = 3600): void;
    public function forget(string $key): void;
}

class RedisCache implements CacheInterface
{
    public function get(string $key): mixed { /* Redis impl */ return null; }
    public function set(string $key, mixed $value, int $ttl = 3600): void { /* */ }
    public function forget(string $key): void { /* */ }
}

class ArrayCache implements CacheInterface
{
    private array $store = [];
    public function get(string $key): mixed { return $this->store[$key] ?? null; }
    public function set(string $key, mixed $value, int $ttl = 3600): void { $this->store[$key] = $value; }
    public function forget(string $key): void { unset($this->store[$key]); }
}

// Service depends on interface — works with either implementation
class UserService
{
    public function __construct(private CacheInterface $cache) {}

    public function findUser(int $id): ?array
    {
        return $this->cache->get("user:$id"); // works with Redis or Array
    }
}

// Tests use ArrayCache, production uses RedisCache — same UserService
$testService = new UserService(new ArrayCache());
$prodService = new UserService(new RedisCache());