PHP Interfaces

La Lakshmi Priya Updated 19 Oct 2026
6 min read ·Lesson 136 of 143

What Is an Interface?

In simple terms: An interface is a contract. It lists the methods a class promises to provide, without saying how they work. A charging port standard is a good comparison: any phone with that port works with any charger with that plug, regardless of the manufacturer. In code, any class that implements a Notifier interface can be used wherever a notifier is needed — email, SMS, WhatsApp, or a test double.

Technically: An interface is declared with the interface keyword and contains public method signatures (and constants; since PHP 8.4 also property declarations). A class uses implements to promise that it provides all of these methods with compatible signatures. Interfaces are types: they can be used in parameter, property, and return type declarations and with instanceof. A class can implement any number of interfaces.

Why Interfaces Matter

BenefitExplanation
InterchangeabilityCode depends on what an object can do, not which class it is
DecouplingA service using a PaymentGateway interface does not change when you switch providers
TestabilityTests can supply simple fake implementations
Multiple rolesA class can be Countable, JsonSerializable, and IteratorAggregate at once
Clear contractsThe interface documents exactly what implementations must provide

Declaring and Implementing an Interface

Syntax

interface InterfaceName
{
    public function methodName(Type $param): ReturnType;
}

class ClassName implements InterfaceName
{
    public function methodName(Type $param): ReturnType
    {
        // implementation
    }
}

Example: Interchangeable Notifiers

<?php
interface Notifier
{
    public function send(string $recipient, string $message): void;
}

final class EmailNotifier implements Notifier
{
    public function send(string $recipient, string $message): void
    {
        echo "Email to $recipient: $message\n";
    }
}

final class SmsNotifier implements Notifier
{
    public function send(string $recipient, string $message): void
    {
        echo "SMS to $recipient: " . mb_substr($message, 0, 27) . "\n";
    }
}

final class OrderService
{
    public function __construct(private Notifier $notifier) {}

    public function ship(int $orderId, string $recipient): void
    {
        // ... update the order ...
        $this->notifier->send($recipient, "Order #$orderId has been shipped and is on its way.");
    }
}

(new OrderService(new EmailNotifier()))->ship(501, 'asha@example.com');
(new OrderService(new SmsNotifier()))->ship(502, '9876543210');

Output

Email to asha@example.com: Order #501 has been shipped and is on its way.
SMS to 9876543210: Order #502 has been shipped

How It Works

  1. Notifier defines the contract: a send() method with this exact signature.
  2. EmailNotifier and SmsNotifier implement it in completely different ways (the SMS version shortens the message to 27 characters).
  3. OrderService declares its dependency as Notifier — the interface, not a concrete class. It works with any implementation and never needs to change when a new channel is added.
  4. Passing an object that does not implement Notifier would cause a TypeError.

Testing With Fake Implementations

Interfaces make it easy to replace real services in tests:

<?php
interface Notifier
{
    public function send(string $recipient, string $message): void;
}

final class InMemoryNotifier implements Notifier
{
    public array $sent = [];

    public function send(string $recipient, string $message): void
    {
        $this->sent[] = [$recipient, $message];
    }
}

final class OrderService
{
    public function __construct(private Notifier $notifier) {}

    public function ship(int $orderId, string $recipient): void
    {
        $this->notifier->send($recipient, "Order #$orderId has been shipped.");
    }
}

$fake = new InMemoryNotifier();
(new OrderService($fake))->ship(501, 'asha@example.com');

var_dump(count($fake->sent) === 1);
echo $fake->sent[0][1] . "\n";

Output

bool(true)
Order #501 has been shipped.

The test checks what the service sent without sending a real email or SMS.


Rules for Interfaces

RuleDetail
All interface methods are publicImplementations must be public too
Methods have no bodyInterfaces describe what, not how
Implementations need compatible signaturesSame rules as overriding: wider parameter types, narrower return types
All methods must be implementedOtherwise the class must be declared abstract
Interfaces cannot be instantiatednew Notifier() is an error
Constants are allowedNotifier::MAX_LENGTH; overridable by implementers since PHP 8.1 unless final

Forgetting a Method

final class WhatsAppNotifier implements Notifier
{
    // send() is missing
}
PHP Fatal error:  Class WhatsAppNotifier contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Notifier::send)

Implementing Several Interfaces

A class can fulfill several contracts at once. PHP's built-in interfaces let your objects work with core language features:

<?php
final class Cart implements Countable, IteratorAggregate, JsonSerializable
{
    private array $items = [];

    public function add(string $sku, int $quantity): void
    {
        $this->items[$sku] = ($this->items[$sku] ?? 0) + $quantity;
    }

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

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

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

$cart = new Cart();
$cart->add('PEN-01', 3);
$cart->add('NB-A5', 2);

echo count($cart) . " items\n";

foreach ($cart as $sku => $quantity) {
    echo "$sku × $quantity\n";
}

echo json_encode($cart) . "\n";

Output

5 items
PEN-01 × 3
NB-A5 × 2
{"items":{"PEN-01":3,"NB-A5":2},"count":5}
InterfaceEnables
Countablecount($object)
IteratorAggregate / Iteratorforeach ($object as ...) (see Iterators)
JsonSerializableCustom output for json_encode()
ArrayAccess$object['key'] syntax
StringableObjects with __toString() (implemented automatically since PHP 8.0)

Interface Inheritance

Interfaces can extend one or more other interfaces, combining their contracts:

<?php
interface HasWeight
{
    public function weightKg(): float;
}

interface HasDimensions
{
    public function volumeCm3(): float;
}

interface Shippable extends HasWeight, HasDimensions
{
    public const MAX_WEIGHT_KG = 30;
}

final class Parcel implements Shippable
{
    public function __construct(private float $kg, private float $l, private float $w, private float $h) {}

    public function weightKg(): float
    {
        return $this->kg;
    }

    public function volumeCm3(): float
    {
        return $this->l * $this->w * $this->h;
    }
}

$parcel = new Parcel(2.4, 30, 20, 10);

var_dump($parcel instanceof HasWeight);
echo $parcel->volumeCm3() . " cm³, max " . Shippable::MAX_WEIGHT_KG . " kg\n";

Output

bool(true)
6000 cm³, max 30 kg

A Parcel is a Shippable, a HasWeight, and a HasDimensions. Small, focused interfaces combined like this follow the Interface Segregation Principle: classes should not be forced to implement methods they do not need.


Interfaces vs Abstract Classes

InterfaceAbstract Class
PurposeDefine a contractShare implementation among related classes
CodeNo method bodiesCan contain implemented methods
StateConstants only (property declarations in PHP 8.4, with hooks)Properties and constructors
How many per classManyOne

A common design uses both: an interface as the type everyone depends on, and an optional abstract base class that implements the interface and provides shared code for implementations that want it.

Naming

PHP's own interfaces use adjectives (Countable, Stringable) or nouns (Iterator). In applications, names describing the role are common: Notifier, PaymentGateway, ProductRepository. Some projects add a suffix (NotifierInterface); choose one convention and use it consistently.


Common Mistakes

Depending on Concrete Classes

Effect: OrderService that requires EmailNotifier cannot use SMS or a test fake.

Fix: Type-hint the interface.

Huge Interfaces

Effect: Implementations must provide many methods they do not need.

Fix: Split interfaces by role and combine them where needed.

Non-Public Implementations

Effect: Fatal error; interface methods must be public.

Fix: Declare implemented methods public.

Interfaces With Only One Implementation "Just in Case"

Effect: Extra files without benefit.

Fix: Introduce an interface when there are multiple implementations, a test double is needed, or a library boundary requires it.


Best Practices

  • Define interfaces for roles that can have several implementations (gateways, notifiers, storage, repositories).
  • Type-hint interfaces in constructors and method parameters.
  • Keep interfaces small and focused; combine them through interface inheritance.
  • Implement built-in interfaces (Countable, IteratorAggregate, JsonSerializable) to integrate objects with the language.
  • Use fake implementations of interfaces in tests.

Official reference: https://www.php.net/manual/en/language.oop5.interfaces.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning
Ask about this post
AI Ask about this post

Ask questions about PHP Interfaces and get answers drawn from it.

Signed-in readers only.