ZetCode

PHP interfaces

last modified May 18, 2025

Interfaces in PHP define contracts that classes must follow. They specify what methods a class must implement without defining how these methods should work. Interfaces enable polymorphism, allowing different classes to be used interchangeably if they implement the same interface.

Basic interface

A simple interface defines method signatures that implementing classes must provide. The interface itself contains no method implementations.

basic_interface.php
<?php

declare (strict_types=1);

interface Logger {
    public function log(string $message): void;
}

class FileLogger implements Logger {
    public function log(string $message): void {
        echo "Logging to file: $message\n";
    }
}

class DatabaseLogger implements Logger {
    public function log(string $message): void {
        echo "Logging to database: $message\n";
    }
}

$fileLogger = new FileLogger();
$fileLogger->log("Error occurred!");

$dbLogger = new DatabaseLogger();
$dbLogger->log("Error occurred!");

This example shows a basic Logger interface with one method that both FileLogger and DatabaseLogger implement. While they perform different actions, both classes adhere to the same interface contract.

λ php basic_interface.php
Logging to file: Error occurred!
Logging to database: Error occurred!

Multiple interfaces

A class can implement multiple interfaces, allowing it to fulfill multiple contracts. This provides more flexibility than inheritance since PHP doesn't support multiple inheritance of classes.

multiple_interfaces.php
<?php

declare (strict_types=1);

interface Loggable {
    public function log(string $message): void;
}

interface Cacheable {
    public function cache(string $key, string $value): void;
    public function getFromCache(string $key): ?string;
}

class Application implements Loggable, Cacheable {
    private array $cache = [];
    
    public function log(string $message): void {
        echo "Application log: $message\n";
    }
    
    public function cache(string $key, string $value): void {
        $this->cache[$key] = $value;
    }
    
    public function getFromCache(string $key): ?string {
        return $this->cache[$key] ?? null;
    }
}

$app = new Application();
$app->log("Starting up");
$app->cache("config", "debug=true");
echo $app->getFromCache("config") . "\n";

The Application class implements both Loggable and Cacheable interfaces, providing implementations for all required methods. This allows the class to be used in contexts expecting either interface.

λ php multiple_interfaces.php
Application log: Starting up
debug=true

Interface inheritance

Interfaces can extend other interfaces, inheriting their method signatures. This allows creating hierarchies of contracts.

interface_inheritance.php
<?php

declare (strict_types=1);

interface Readable {
    public function read(): string;
}

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

interface ReadWritable extends Readable, Writable {
    public function clear(): void;
}

class FileStorage implements ReadWritable {
    public function read(): string {
        return "Reading data from file";
    }
    
    public function write(string $data): void {
        echo "Writing data to file: $data\n";
    }
    
    public function clear(): void {
        echo "Clearing file storage\n";
    }
}

$storage = new FileStorage();
echo $storage->read() . "\n";
$storage->write("Test data");
$storage->clear();

The ReadWritable interface extends both Readable and Writable, and adds an additional method. The FileStorage class must implement all methods from all three interfaces.

λ php interface_inheritance.php
Reading data from file
Writing data to file: Test data
Clearing file storage

Polymorphism with interfaces

Interfaces enable polymorphism - the ability to use different classes interchangeably if they implement the same interface. This is powerful for creating flexible systems.

polymorphism.php
<?php

declare (strict_types=1);

interface PaymentGateway {
    public function processPayment(float $amount): bool;
}

class StripeGateway implements PaymentGateway {
    public function processPayment(float $amount): bool {
        echo "Processing Stripe payment for \$$amount\n";
        return true;
    }
}

class PayPalGateway implements PaymentGateway {
    public function processPayment(float $amount): bool {
        echo "Processing PayPal payment for \$$amount\n";
        return true;
    }
}

class OrderProcessor {
    public function __construct(private PaymentGateway $gateway) {}
    
    public function processOrder(float $amount): bool {
        return $this->gateway->processPayment($amount);
    }
}

// Can use either payment gateway interchangeably
$stripeProcessor = new OrderProcessor(new StripeGateway());
$stripeProcessor->processOrder(100.50);

$paypalProcessor = new OrderProcessor(new PayPalGateway());
$paypalProcessor->processOrder(75.25);

The OrderProcessor doesn't need to know which specific payment gateway it's using - it only cares that it implements the PaymentGateway interface. This makes the code more flexible and easier to maintain.

λ php polymorphism.php
Processing Stripe payment for $100.5
Processing PayPal payment for $75.25

Interfaces vs abstract classes

While similar, interfaces and abstract classes serve different purposes. Interfaces define contracts without implementation, while abstract classes can provide partial implementation.

comparison.php
<?php

declare (strict_types=1);

// Interface - pure contract
interface Logger {
    public function log(string $message): void;
}

// Abstract class - can provide some implementation
abstract class AbstractLogger {
    protected string $logPrefix = '';
    
    abstract public function log(string $message): void;
    
    public function setPrefix(string $prefix): void {
        $this->logPrefix = $prefix;
    }
}

class FileLogger extends AbstractLogger implements Logger {
    public function log(string $message): void {
        echo "[{$this->logPrefix}] Log to file: $message\n";
    }
}

$logger = new FileLogger();
$logger->setPrefix("FILE");
$logger->log("Test message");

This example shows a class that both extends an abstract class and implements an interface. The abstract class provides some shared functionality (setPrefix), while the interface guarantees the log method exists.

λ php comparison.php
[FILE] Log to file: Test message

Interfaces vs Traits

While interfaces define contracts that classes must follow, traits are a mechanism for code reuse in PHP. Traits allow you to include methods in multiple classes, helping to avoid code duplication. Unlike interfaces, traits can provide actual method implementations, but they cannot define contracts or be instantiated on their own.

interfaces_vs_traits.php
<?php

declare (strict_types=1);

interface Logger {
    public function log(string $message): void;
}

trait FileLogTrait {
    public function logToFile(string $message): void {
        echo "File log: $message\n";
    }
}

class MyService implements Logger {
    use FileLogTrait;
    public function log(string $message): void {
        // Use trait method for actual logging
        $this->logToFile($message);
    }
}

$service = new MyService();
$service->log("Hello from trait!");

In this example, the Logger interface defines a contract, while the FileLogTrait trait provides a reusable method. The MyService class implements the interface and uses the trait to fulfill the contract.

Traits and interfaces can be combined for maximum flexibility and code reuse in PHP applications.

Type hinting with interfaces

Interfaces are particularly useful for type hinting parameters, return types, and properties. This ensures code works with any implementation of the interface.

type_hinting.php
<?php

declare (strict_types=1);

interface Notifier {
    public function sendNotification(string $message): void;
}

class EmailNotifier implements Notifier {
    public function sendNotification(string $message): void {
        echo "Sending email: $message\n";
    }
}

class SMSNotifier implements Notifier {
    public function sendNotification(string $message): void {
        echo "Sending SMS: $message\n";
    }
}

class NotificationService {
    public function __construct(private Notifier $notifier) {}
    
    public function notify(string $message): void {
        $this->notifier->sendNotification($message);
    }
}

$emailService = new NotificationService(new EmailNotifier());
$emailService->notify("Hello via email");

$smsService = new NotificationService(new SMSNotifier());
$smsService->notify("Hello via SMS");

The NotificationService doesn't care which specific notifier it receives - it only requires that it implements the Notifier interface. This makes the service much more flexible and testable.

λ php type_hinting.php
Sending email: Hello via email
Sending SMS: Hello via SMS

Best practices

Following best practices when working with interfaces leads to cleaner, more maintainable code. Here are some key guidelines.

best_practices.php
<?php

declare (strict_types=1);

// 1. Keep interfaces small and focused (Single Responsibility Principle)
interface UserAuthenticator {
    public function authenticate(string $username, string $password): bool;
}

// 2. Name interfaces clearly (often with adjective or -able suffix)
interface Cacheable {
    public function cache(string $key, mixed $value): void;
    public function getCached(string $key): mixed;
}

// 3. Use interfaces for dependency injection
class OrderService {
    public function __construct(private Logger $logger) {}
    
    public function processOrder(Order $order): void {
        $this->logger->log("Processing order #{$order->getId()}");
        // Process order...
    }
}

// 4. Document interface methods with PHPDoc
interface DataTransformer {
    /**
     * Transforms data from one format to another
     * @param mixed $input The input data to transform
     * @return mixed The transformed data
     * @throws InvalidArgumentException If input is invalid
     */
    public function transform($input);
}

// 5. Prefer interface type hints over concrete classes
function sendAlert(Notifier $notifier, string $message): void {
    $notifier->sendNotification($message);
}

// 6. Consider using interfaces for testing/mocking
interface DatabaseConnection {
    public function query(string $sql): array;
}

// In tests:
class MockDatabaseConnection implements DatabaseConnection {
    public function query(string $sql): array {
        return ['test' => 'data']; // Return test data without real DB
    }
}

These practices help create interfaces that are easy to understand, implement, and maintain. Small, well-named interfaces with good documentation make code more flexible and testable.

In this tutorial, we explored the concept of interfaces in PHP. We discussed how to define and implement interfaces, their benefits for code organization, and how they enable polymorphism. We also compared interfaces with abstract classes, highlighting their differences and when to use each. Finally, we covered best practices for working with interfaces to ensure clean and maintainable code.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all PHP tutorials.