Skip to main content

Course Progress

Loading...

Interfaces in PHP: Building Flexible Contracts

Duration: 45 minutes
Module 2: Object-Oriented PHP

Learning Objectives

  • Master PHP programming concepts
  • Write clean, maintainable code
  • Apply best practices
  • Build dynamic applications

Understanding Contracts in Code

Welcome to our exploration of interfaces in PHP! In our previous lessons, we've covered classes, inheritance, and abstract classes. Today, we'll be diving into interfaces - one of the most powerful tools in object-oriented programming for creating flexible, maintainable systems.

Why Interfaces Matter: In WordPress development, interfaces form the backbone of plugin systems, hook implementations, and API integrations. Understanding interfaces will dramatically improve your ability to create extensible plugins and themes that other developers can easily build upon.

What Are Interfaces?

Think of an interface as a contract or a promise. When a class implements an interface, it's making a promise to provide specific functionality. It's like a restaurant signing up for a food delivery service - the restaurant promises to prepare food according to certain standards, while the delivery service handles getting it to customers.

Interface Relationship

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class Loggable { > +log(message) +get...

In this diagram, the Loggable interface defines a contract that any class wanting to be "loggable" must fulfill. Multiple completely different classes can implement this interface, each providing its own implementation of the required methods.

The Orchestra Analogy

Think of an interface as a musical score, and classes as different musicians. The score (interface) dictates what notes must be played (methods that must be implemented), but each musician (class) plays those notes with their own instrument and style. A violin and a piano can both play the same melody (implement the same interface), but they do so in their own unique ways.

This allows a conductor (your application) to work with any instrument that can play the required score, without needing to know the specifics of how each instrument produces its sound.

Interface Syntax in PHP

In PHP, interfaces are declared using the interface keyword, followed by the interface name. By convention, interface names often end with "able" or "Interface" (e.g., Countable, LoggerInterface) to make their purpose clear.

Basic Interface Syntax


// Define an interface
interface Loggable {
    // Method signatures (no implementation)
    public function log($message);
    public function getLogHistory();
}

// Class implementing the interface
class FileLogger implements Loggable {
    private $logFile;
    private $logHistory = [];
    
    public function __construct($logFile) {
        $this->logFile = $logFile;
    }
    
    // Must implement all interface methods
    public function log($message) {
        $logEntry = date('Y-m-d H:i:s') . ': ' . $message;
        $this->logHistory[] = $logEntry;
        
        // Write to log file
        file_put_contents($this->logFile, $logEntry . PHP_EOL, FILE_APPEND);
    }
    
    public function getLogHistory() {
        return $this->logHistory;
    }
    
    // Can add additional methods not in the interface
    public function clearLog() {
        $this->logHistory = [];
        file_put_contents($this->logFile, '');
    }
}

// Another class implementing the same interface
class DatabaseLogger implements Loggable {
    private $dbConnection;
    private $logHistory = [];
    
    public function __construct($dbConnection) {
        $this->dbConnection = $dbConnection;
    }
    
    // Different implementation of the same interface methods
    public function log($message) {
        $logEntry = date('Y-m-d H:i:s') . ': ' . $message;
        $this->logHistory[] = $logEntry;
        
        // Log to database (simplified example)
        $query = "INSERT INTO logs (message, created_at) VALUES ('{$message}', NOW())";
        // $this->dbConnection->exec($query);
    }
    
    public function getLogHistory() {
        return $this->logHistory;
    }
}

// Usage: can use either logger interchangeably
function processUserAction(Loggable $logger, $action) {
    // ... process the action ...
    $logger->log("User performed action: {$action}");
    
    // Return log history
    return $logger->getLogHistory();
}

// Works with any class that implements Loggable
$fileLogger = new FileLogger('/path/to/log.txt');
$dbLogger = new DatabaseLogger($dbConnection);

$fileLogHistory = processUserAction($fileLogger, 'login');
$dbLogHistory = processUserAction($dbLogger, 'update_profile');
                

Key Rules for Interfaces

  • Interfaces can only contain method declarations, not implementations
  • All methods declared in an interface must be public
  • A class that implements an interface must implement ALL methods declared in the interface
  • A class can implement multiple interfaces
  • Interfaces can extend other interfaces
  • Interfaces cannot be instantiated directly
  • Interfaces can contain constants but no properties

Interfaces vs. Abstract Classes

In our previous lesson on abstract classes, we explored how they provide a partial implementation for child classes. Interfaces are different - they provide no implementation at all, only a contract. Let's compare the two:

Feature Interface Abstract Class
Instantiation Cannot be instantiated Cannot be instantiated
Method implementation No implementation (PHP 8+ allows default implementations) Can have both abstract and concrete methods
Properties Cannot have properties (only constants) Can have properties
Multiple inheritance A class can implement multiple interfaces A class can extend only one abstract class
Access modifiers All methods are implicitly public Can use public, protected, private
Constructor Cannot have a constructor Can have a constructor
Use case "Can-do" relationship (capabilities) "Is-a" relationship with shared implementation

The Blueprint vs. Contract Analogy

Think of abstract classes as partial blueprints for a building. They specify some complete rooms (concrete methods) and some unfinished areas (abstract methods) that must be completed by the builder.

Interfaces, on the other hand, are like a contract with a list of requirements. They don't tell you how to build anything; they just specify what features the final building must have (e.g., "must have four bedrooms, three bathrooms").

A building can only follow one blueprint (extend one abstract class) but can satisfy multiple contracts (implement multiple interfaces).

When to Use Each Approach


// Use an abstract class when you have an "is-a" relationship with shared code
abstract class Animal {
    protected $name;
    
    public function __construct($name) {
        $this->name = $name;
    }
    
    // Shared method implementation
    public function getName() {
        return $this->name;
    }
    
    // Method to be implemented by subclasses
    abstract public function makeSound();
}

class Dog extends Animal {
    public function makeSound() {
        return "Woof!";
    }
}

// Use interfaces for "can-do" capabilities that unrelated classes might share
interface Swimmable {
    public function swim();
    public function getMaxDepth();
}

// These classes aren't related, but both can swim
class Duck extends Animal implements Swimmable {
    public function makeSound() {
        return "Quack!";
    }
    
    public function swim() {
        return "{$this->name} is paddling across the pond.";
    }
    
    public function getMaxDepth() {
        return 2; // meters
    }
}

class Submarine implements Swimmable {
    private $name;
    private $maxDepth;
    
    public function __construct($name, $maxDepth) {
        $this->name = $name;
        $this->maxDepth = $maxDepth;
    }
    
    public function swim() {
        return "{$this->name} is diving beneath the waves.";
    }
    
    public function getMaxDepth() {
        return $this->maxDepth;
    }
}

// Function that works with any Swimmable object
function startRescueMission(Swimmable $rescuer, $depth) {
    if ($depth <= $rescuer->getMaxDepth()) {
        echo "Rescue mission started! ";
        echo $rescuer->swim();
    } else {
        echo "This rescuer cannot dive to the required depth!";
    }
}

$duck = new Duck("Donald");
$submarine = new Submarine("Deep Explorer", 1000);

startRescueMission($duck, 1); // Works with a Duck
startRescueMission($submarine, 500); // Works with a Submarine
                

Implementing Multiple Interfaces

One of the most powerful aspects of interfaces is that a class can implement multiple interfaces. This allows for a form of multiple inheritance in PHP, which doesn't support extending multiple classes.

Multiple Interface Implementation

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class Serializable { > +serialize() +...

Implementing Multiple Interfaces


interface Serializable {
    public function serialize();
    public function unserialize($data);
}

interface JsonSerializable {
    public function jsonSerialize();
}

interface Loggable {
    public function log($message);
    public function getLogHistory();
}

class UserProfile implements Serializable, JsonSerializable, Loggable {
    private $id;
    private $username;
    private $email;
    private $logHistory = [];
    
    public function __construct($id, $username, $email) {
        $this->id = $id;
        $this->username = $username;
        $this->email = $email;
    }
    
    // Serializable implementation
    public function serialize() {
        return serialize([
            'id' => $this->id,
            'username' => $this->username,
            'email' => $this->email
        ]);
    }
    
    public function unserialize($data) {
        $data = unserialize($data);
        $this->id = $data['id'];
        $this->username = $data['username'];
        $this->email = $data['email'];
    }
    
    // JsonSerializable implementation
    public function jsonSerialize() {
        return [
            'id' => $this->id,
            'username' => $this->username,
            'email' => $this->email
        ];
    }
    
    // Loggable implementation
    public function log($message) {
        $this->logHistory[] = date('Y-m-d H:i:s') . ': ' . $message;
    }
    
    public function getLogHistory() {
        return $this->logHistory;
    }
    
    // Additional class-specific methods
    public function getUsername() {
        return $this->username;
    }
    
    public function getEmail() {
        return $this->email;
    }
}

// Usage example
$user = new UserProfile(1, 'johndoe', 'john@example.com');

// Using Loggable functionality
$user->log('Profile viewed');
$user->log('Profile updated');
print_r($user->getLogHistory());

// Using JsonSerializable functionality
$json = json_encode($user);
echo $json; // {"id":1,"username":"johndoe","email":"john@example.com"}

// Using Serializable functionality
$serialized = $user->serialize();
$newUser = new UserProfile(0, '', '');
$newUser->unserialize($serialized);
echo $newUser->getUsername(); // "johndoe"
                

Interface Segregation Principle

The Interface Segregation Principle (ISP) is one of the SOLID principles of object-oriented design. It states that "clients should not be forced to depend on methods they do not use." In practice, this means it's better to have many small, focused interfaces rather than a few large, general-purpose ones.

Before (violating ISP)


// One large interface - forces implementing classes to define methods they might not need
interface WorkerInterface {
    public function work();
    public function eat();
    public function sleep();
}

class Robot implements WorkerInterface {
    public function work() {
        return "Robot is working";
    }
    
    public function eat() {
        // Robots don't eat, but forced to implement this
        return null;
    }
    
    public function sleep() {
        // Robots don't sleep, but forced to implement this
        return null;
    }
}
                    

After (following ISP)


// Segregated into smaller, focused interfaces
interface Workable {
    public function work();
}

interface Eatable {
    public function eat();
}

interface Sleepable {
    public function sleep();
}

// Human needs all capabilities
class Human implements Workable, Eatable, Sleepable {
    public function work() {
        return "Human is working";
    }
    
    public function eat() {
        return "Human is eating";
    }
    
    public function sleep() {
        return "Human is sleeping";
    }
}

// Robot only implements what it needs
class Robot implements Workable {
    public function work() {
        return "Robot is working";
    }
}
                    

Interface Inheritance

Just as classes can extend other classes, interfaces can extend other interfaces. This allows you to create hierarchies of interface contracts, where a more specific interface includes all the requirements of a more general one, plus additional requirements.

Interface Inheritance

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class Readable { > +read() } class Wr...

Interface Inheritance Example


// Basic interfaces
interface Readable {
    public function read();
}

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

// Combined interface extending both Readable and Writable
interface Storage extends Readable, Writable {
    public function delete();
}

// More specialized interface
interface AdvancedStorage extends Storage {
    public function search($query);
    public function getSize();
}

// Implementing the specialized interface
class FileStorage implements AdvancedStorage {
    private $filename;
    private $data = [];
    
    public function __construct($filename) {
        $this->filename = $filename;
        // Load data from file if it exists
        if (file_exists($filename)) {
            $this->data = json_decode(file_get_contents($filename), true) ?? [];
        }
    }
    
    // From Readable
    public function read() {
        return $this->data;
    }
    
    // From Writable
    public function write($data) {
        $this->data = $data;
        $this->saveToFile();
        return true;
    }
    
    // From Storage
    public function delete() {
        $this->data = [];
        $this->saveToFile();
        return true;
    }
    
    // From AdvancedStorage
    public function search($query) {
        $results = [];
        foreach ($this->data as $key => $value) {
            if (strpos(json_encode($value), $query) !== false) {
                $results[$key] = $value;
            }
        }
        return $results;
    }
    
    public function getSize() {
        return filesize($this->filename) ?? 0;
    }
    
    // Helper method (not required by any interface)
    private function saveToFile() {
        file_put_contents($this->filename, json_encode($this->data));
    }
}

// Usage with type hinting for different interface levels
function readData(Readable $storage) {
    return $storage->read();
}

function storeData(Writable $storage, $data) {
    $storage->write($data);
    echo "Data stored successfully.";
}

function fullBackup(Storage $storage) {
    $data = $storage->read();
    // Make a backup...
    echo "Backup completed with " . count($data) . " records.";
}

function searchAndReport(AdvancedStorage $storage, $query) {
    $results = $storage->search($query);
    echo "Found " . count($results) . " results. Total storage size: " . $storage->getSize() . " bytes.";
}

// Create a storage instance
$fileStorage = new FileStorage('data.json');

// Works with all functions because it implements all interfaces
readData($fileStorage);
storeData($fileStorage, ['user' => 'John', 'email' => 'john@example.com']);
fullBackup($fileStorage);
searchAndReport($fileStorage, 'john');
                

Type Hinting with Interfaces

One of the most powerful uses of interfaces is in type hinting. By type hinting with interfaces instead of concrete classes, you create more flexible, decoupled code that can work with any class that implements the required interface.

Type Hinting for Flexibility


interface PaymentProcessor {
    public function processPayment($amount);
    public function refundPayment($transactionId);
}

class StripePaymentProcessor implements PaymentProcessor {
    public function processPayment($amount) {
        // Stripe-specific implementation
        return "Stripe payment processed: ${$amount}";
    }
    
    public function refundPayment($transactionId) {
        // Stripe-specific refund
        return "Stripe payment {$transactionId} refunded";
    }
}

class PayPalPaymentProcessor implements PaymentProcessor {
    public function processPayment($amount) {
        // PayPal-specific implementation
        return "PayPal payment processed: ${$amount}";
    }
    
    public function refundPayment($transactionId) {
        // PayPal-specific refund
        return "PayPal payment {$transactionId} refunded";
    }
}

// Flexible order processing that works with any payment processor
class OrderProcessor {
    private $paymentProcessor;
    
    // Type hint with the interface
    public function __construct(PaymentProcessor $paymentProcessor) {
        $this->paymentProcessor = $paymentProcessor;
    }
    
    public function checkout($items) {
        $total = $this->calculateTotal($items);
        
        // Process payment using whatever payment processor was injected
        $transactionId = $this->paymentProcessor->processPayment($total);
        
        return [
            'status' => 'success',
            'total' => $total,
            'transaction_id' => $transactionId
        ];
    }
    
    private function calculateTotal($items) {
        return array_sum(array_column($items, 'price'));
    }
}

// Usage
$items = [
    ['name' => 'Product 1', 'price' => 29.99],
    ['name' => 'Product 2', 'price' => 49.99]
];

// Can use any payment processor
$stripeProcessor = new StripePaymentProcessor();
$paypalProcessor = new PayPalPaymentProcessor();

// Create order processor with Stripe
$orderProcessor = new OrderProcessor($stripeProcessor);
$stripeResult = $orderProcessor->checkout($items);

// Create order processor with PayPal
$orderProcessor = new OrderProcessor($paypalProcessor);
$paypalResult = $orderProcessor->checkout($items);
                

Dependency Inversion Principle

The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules; both should depend on abstractions. Interfaces are perfect for creating these abstractions.

Diagram
> F[Interface] F Without Interfaces High-Level Module Low-Level Module With Interfaces High-Level Module Interface Low-Level Module

By having the high-level module (like OrderProcessor) depend on an interface (PaymentProcessor) rather than concrete implementations (StripePaymentProcessor, PayPalPaymentProcessor), we create more flexible, maintainable code.

Constants in Interfaces

While interfaces cannot contain properties, they can define constants. These constants are implicitly public and can be accessed through the interface name. Any class implementing the interface also inherits these constants.

Interface Constants Example


interface PaymentStatus {
    // Constants
    const PENDING = 'pending';
    const COMPLETED = 'completed';
    const FAILED = 'failed';
    const REFUNDED = 'refunded';
}

class Payment implements PaymentStatus {
    private $status;
    
    public function __construct() {
        // Access constant from interface
        $this->status = self::PENDING;
    }
    
    public function processPayment() {
        // Simulate payment processing
        $success = (rand(0, 10) > 2); // 80% success rate
        
        // Update status based on result
        if ($success) {
            $this->status = self::COMPLETED;
            return true;
        } else {
            $this->status = self::FAILED;
            return false;
        }
    }
    
    public function refund() {
        if ($this->status !== self::COMPLETED) {
            throw new Exception("Cannot refund a payment that isn't completed");
        }
        
        $this->status = self::REFUNDED;
        return true;
    }
    
    public function getStatus() {
        return $this->status;
    }
}

// Even without implementing the interface, you can access the constants
echo "Statuses: " . PaymentStatus::PENDING . ", " . PaymentStatus::COMPLETED;

// Create a payment
$payment = new Payment();
echo "Initial status: " . $payment->getStatus(); // "pending"

if ($payment->processPayment()) {
    echo "Payment completed successfully.";
} else {
    echo "Payment failed.";
}

// Access via implementing class
if ($payment->getStatus() === Payment::COMPLETED) {
    $payment->refund();
    echo "Payment refunded.";
}
                

Using Interface Constants for Type Safety

Interface constants are great for defining a fixed set of values that a method should accept. This provides a form of type safety in PHP.


interface LogLevel {
    const DEBUG = 'debug';
    const INFO = 'info';
    const WARNING = 'warning';
    const ERROR = 'error';
}

class Logger implements LogLevel {
    private $logEntries = [];
    
    public function log($message, $level = self::INFO) {
        // Validate level using interface constants
        $validLevels = [self::DEBUG, self::INFO, self::WARNING, self::ERROR];
        
        if (!in_array($level, $validLevels)) {
            throw new InvalidArgumentException("Invalid log level");
        }
        
        $this->logEntries[] = [
            'timestamp' => date('Y-m-d H:i:s'),
            'level' => $level,
            'message' => $message
        ];
    }
    
    // Rest of the class...
}

// Usage
$logger = new Logger();
$logger->log("User logged in", LogLevel::INFO);
$logger->log("Failed login attempt", LogLevel::WARNING);
$logger->log("Database connection failed", LogLevel::ERROR);

// Invalid level would throw an exception
// $logger->log("Test message", "invalid_level");
                

Default Method Implementations (PHP 8.0+)

Starting with PHP 8.0, interfaces can include default implementations for methods. This brings interfaces closer to abstract classes, but with the key difference that a class can still implement multiple interfaces.

Interface with Default Methods


// PHP 8.0+ only
interface Loggable {
    // Method with default implementation
    public function log($message) {
        // Default logging to error_log
        error_log("[" . date('Y-m-d H:i:s') . "] {$message}");
    }
    
    // Method without default implementation
    public function getLogHistory();
}

// Implementing class can use the default implementation
class SimpleLogger implements Loggable {
    private $logHistory = [];
    
    // Uses default log() implementation from interface
    
    // Must implement the method without a default
    public function getLogHistory() {
        return $this->logHistory;
    }
}

// Implementing class can override the default implementation
class CustomLogger implements Loggable {
    private $logHistory = [];
    
    // Override the default implementation
    public function log($message) {
        $logEntry = "[" . date('Y-m-d H:i:s') . "] {$message}";
        $this->logHistory[] = $logEntry;
        
        // Custom logging to both error_log and file
        error_log($logEntry);
        file_put_contents('custom.log', $logEntry . PHP_EOL, FILE_APPEND);
    }
    
    public function getLogHistory() {
        return $this->logHistory;
    }
}
                

Compatibility Note: Default method implementations in interfaces are only available in PHP 8.0 and later. If you're working in an environment with older PHP versions (which is common with WordPress installations), you'll need to stick with the traditional approach of interfaces without implementations.

Real-World Examples in WordPress

WordPress makes extensive use of interfaces in its codebase, especially in more recent versions and in modern plugins. Let's explore a few real-world examples:

WP_Hook_Interface

WordPress has an interface for its hooks system, which is the foundation of WordPress's plugin architecture.

Simplified WP_Hook_Interface Example


/**
 * Interface for all hooks (actions and filters).
 */
interface WP_Hook_Interface {
    /**
     * Adds a callback function to a hook.
     *
     * @param string   $tag             The hook name.
     * @param callable $function_to_add The callback function.
     * @param int      $priority        The priority.
     * @param int      $accepted_args   The number of args the callback accepts.
     * @return true
     */
    public function add_filter($tag, $function_to_add, $priority, $accepted_args);

    /**
     * Removes a callback function from a hook.
     *
     * @param string   $tag                The hook name.
     * @param callable $function_to_remove The callback to remove.
     * @param int      $priority           The priority.
     * @return bool True if removed, false if not found.
     */
    public function remove_filter($tag, $function_to_remove, $priority);

    /**
     * Executes a hook's callbacks.
     *
     * @param string $tag   The hook name.
     * @param mixed  $value The value to filter.
     * @param array  $args  Additional arguments.
     * @return mixed The filtered value.
     */
    public function apply_filters($tag, $value, $args);

    /**
     * Executes an action hook.
     *
     * @param string $tag  The hook name.
     * @param array  $args The action arguments.
     */
    public function do_action($tag, $args);

    /**
     * Checks if a hook has callbacks.
     *
     * @param string $tag      The hook name.
     * @param bool   $priority Optional. Whether to check a specific priority.
     * @return bool|int
     */
    public function has_filter($tag, $priority = false);
}

// WordPress implements this interface with the WP_Hook class
class WP_Hook implements WP_Hook_Interface {
    // Implementation of all the required methods
}
                

This interface allows WordPress to potentially swap out its hook implementation while maintaining backward compatibility with plugins that rely on the hook system.

WP_HTTP_Response_Interface

WordPress uses interfaces for its HTTP response handling, particularly in the REST API.

WordPress HTTP Response Interface Example


/**
 * Interface for HTTP responses.
 */
interface WP_HTTP_Response_Interface {
    /**
     * Gets the response data.
     *
     * @return mixed The response data.
     */
    public function get_data();

    /**
     * Sets the response data.
     *
     * @param mixed $data Response data.
     * @return $this
     */
    public function set_data($data);

    /**
     * Gets the response status code.
     *
     * @return int The response status code.
     */
    public function get_status();

    /**
     * Sets the response status code.
     *
     * @param int $code The status code.
     * @return $this
     */
    public function set_status($code);

    /**
     * Gets the response headers.
     *
     * @return array The response headers.
     */
    public function get_headers();

    /**
     * Sets the response headers.
     *
     * @param array $headers The headers to set.
     * @return $this
     */
    public function set_headers($headers);
}

// WordPress has multiple implementations
class WP_REST_Response implements WP_HTTP_Response_Interface {
    // Implementation for REST API responses
}

class WP_HTTP_Response implements WP_HTTP_Response_Interface {
    // Base implementation for HTTP responses
}
                

Creating a Custom Widget Interface

While WordPress's widget system pre-dates extensive use of interfaces, we can demonstrate how a modern approach might use interfaces for widget development:

Custom Widget Interface Example


/**
 * Interface for custom widgets.
 */
interface CustomWidgetInterface {
    /**
     * Renders the widget on the front end.
     *
     * @param array $args     Display arguments.
     * @param array $instance Settings for this widget instance.
     */
    public function render_widget($args, $instance);
    
    /**
     * Renders the widget form in the admin.
     *
     * @param array $instance Current settings.
     */
    public function render_form($instance);
    
    /**
     * Updates the widget settings.
     *
     * @param array $new_instance New settings.
     * @param array $old_instance Old settings.
     * @return array Updated settings.
     */
    public function update_settings($new_instance, $old_instance);
}

/**
 * Base class that integrates with WordPress widget API
 */
abstract class Base_Custom_Widget extends WP_Widget implements CustomWidgetInterface {
    /**
     * Base_Custom_Widget constructor.
     *
     * @param string $id_base         Optional Base ID.
     * @param string $name            Name.
     * @param array  $widget_options  Optional.
     * @param array  $control_options Optional.
     */
    public function __construct($id_base, $name, $widget_options = [], $control_options = []) {
        parent::__construct($id_base, $name, $widget_options, $control_options);
    }
    
    /**
     * WordPress widget API method.
     */
    public function widget($args, $instance) {
        $this->render_widget($args, $instance);
    }
    
    /**
     * WordPress widget API method.
     */
    public function form($instance) {
        $this->render_form($instance);
    }
    
    /**
     * WordPress widget API method.
     */
    public function update($new_instance, $old_instance) {
        return $this->update_settings($new_instance, $old_instance);
    }
}

/**
 * Example implementation of a custom widget.
 */
class Recent_Posts_Widget extends Base_Custom_Widget {
    /**
     * Constructor.
     */
    public function __construct() {
        parent::__construct(
            'recent_posts_widget',
            'Recent Posts Widget',
            [
                'description' => 'Displays recent posts with custom formatting.',
            ]
        );
    }
    
    /**
     * Renders the widget.
     */
    public function render_widget($args, $instance) {
        echo $args['before_widget'];
        echo $args['before_title'] . $instance['title'] . $args['after_title'];
        
        // Query recent posts
        $posts = get_posts([
            'numberposts' => $instance['number'],
            'post_status' => 'publish',
        ]);
        
        if ($posts) {
            echo '<ul class="recent-posts">';
            foreach ($posts as $post) {
                echo '<li>';
                echo '<a href="' . get_permalink($post) . '">' . $post->post_title . '</a>';
                echo '</li>';
            }
            echo '</ul>';
        } else {
            echo '<p>No recent posts found.</p>';
        }
        
        echo $args['after_widget'];
    }
    
    /**
     * Renders the form.
     */
    public function render_form($instance) {
        $title = isset($instance['title']) ? $instance['title'] : 'Recent Posts';
        $number = isset($instance['number']) ? (int) $instance['number'] : 5;
        
        echo '<p>';
        echo '<label for="' . $this->get_field_id('title') . '">Title:</label>';
        echo '<input class="widefat" id="' . $this->get_field_id('title') . '" name="' . $this->get_field_name('title') . '" type="text" value="' . esc_attr($title) . '">';
        echo '</p>';
        
        echo '<p>';
        echo '<label for="' . $this->get_field_id('number') . '">Number of posts:</label>';
        echo '<input class="tiny-text" id="' . $this->get_field_id('number') . '" name="' . $this->get_field_name('number') . '" type="number" step="1" min="1" value="' . esc_attr($number) . '" size="3">';
        echo '</p>';
    }
    
    /**
     * Updates the settings.
     */
    public function update_settings($new_instance, $old_instance) {
        $instance = [];
        $instance['title'] = !empty($new_instance['title']) ? sanitize_text_field($new_instance['title']) : '';
        $instance['number'] = !empty($new_instance['number']) ? (int) $new_instance['number'] : 5;
        
        return $instance;
    }
}

// Register the widget
function register_recent_posts_widget() {
    register_widget('Recent_Posts_Widget');
}
add_action('widgets_init', 'register_recent_posts_widget');
                

Interface Best Practices

Keep Interfaces Focused and Small

Following the Interface Segregation Principle, create small, focused interfaces rather than large, general-purpose ones. A good rule of thumb is that an interface should have no more than a handful of methods, all related to a single responsibility.

Good: Focused interfaces


interface Readable {
    public function read($id);
}

interface Writable {
    public function create($data);
    public function update($id, $data);
    public function delete($id);
}

// Class can implement only what it needs
class ReadOnlyRepository implements Readable {
    public function read($id) {
        // Implementation
    }
}

class FullRepository implements Readable, Writable {
    public function read($id) {
        // Implementation
    }
    
    public function create($data) {
        // Implementation
    }
    
    public function update($id, $data) {
        // Implementation
    }
    
    public function delete($id) {
        // Implementation
    }
}
                        

Use Descriptive Method Names

Since interfaces are contracts, use clear, descriptive method names that explain what the method does, not how it's implemented.

Good: Descriptive method names


// Good
interface UserRepository {
    public function findUserById($id);
    public function findUsersByRole($role);
    public function saveUser(User $user);
    public function deleteUser($id);
}

// Bad
interface UserRepo {
    public function getFromDatabase($id); // How, not what
    public function executeQuery($role); // How, not what
    public function insertOrUpdate(User $user); // Mixing concerns
    public function executeDelete($id); // How, not what
}
                        

Use Type Hinting

Take advantage of PHP's type hinting capabilities to make interfaces more robust. Use strict types where appropriate.

Good: Type hinting in interfaces


// PHP 7.0+
interface UserRepository {
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function save(User $user): bool;
    public function delete(int $id): bool;
}
                        

Document Interface Methods Thoroughly

Since interfaces are contracts that others will implement, document them thoroughly with PHPDoc comments.

Good: Well-documented interface


/**
 * Interface for payment gateways.
 */
interface PaymentGateway {
    /**
     * Process a payment transaction.
     *
     * @param float  $amount          The amount to charge.
     * @param string $currency        The currency code (e.g., 'USD').
     * @param array  $customer_data   Customer information.
     * @param array  $payment_details Payment method details.
     *
     * @return array Transaction result containing:
     *               - 'success' (bool): Whether the transaction succeeded.
     *               - 'transaction_id' (string): The gateway transaction ID.
     *               - 'message' (string): Success/error message.
     *               - 'error_code' (string|null): Error code if failed.
     *
     * @throws PaymentException If there's a critical error processing the payment.
     */
    public function processPayment(
        float $amount,
        string $currency,
        array $customer_data,
        array $payment_details
    ): array;
    
    /**
     * Refund a previous payment transaction.
     *
     * @param string $transaction_id The transaction ID to refund.
     * @param float  $amount         The amount to refund, or null for full refund.
     *
     * @return bool True if the refund was successful, false otherwise.
     *
     * @throws RefundException If there's a critical error processing the refund.
     */
    public function refundPayment(string $transaction_id, ?float $amount = null): bool;
}
                        

Use Interfaces for Dependency Injection

Interfaces are ideal for dependency injection, allowing you to swap implementations easily.

Good: Dependency injection with interfaces


// Service class depends on an interface, not a concrete class
class NotificationService {
    private $notifier;
    
    // Type hint with the interface
    public function __construct(NotifierInterface $notifier) {
        $this->notifier = $notifier;
    }
    
    public function sendNotification($user, $message) {
        // Use the injected implementation
        $this->notifier->notify($user, $message);
    }
}

// Different implementations can be injected
$emailNotifier = new EmailNotifier();
$smsNotifier = new SMSNotifier();
$pushNotifier = new PushNotifier();

// Create the service with any implementation
$service = new NotificationService($emailNotifier);
// Later can switch to a different implementation
$service = new NotificationService($smsNotifier);
                        

Consider Using Standards Interfaces

PHP includes several standard interfaces (like Iterator, Countable, JsonSerializable) that you can implement to make your classes work with built-in PHP functionality.

Using Standard PHP Interfaces


// Making a class work with count() function
class UserCollection implements Countable, Iterator {
    private $users = [];
    private $position = 0;
    
    public function addUser(User $user) {
        $this->users[] = $user;
    }
    
    // Countable interface implementation
    public function count(): int {
        return count($this->users);
    }
    
    // Iterator interface implementation
    public function current() {
        return $this->users[$this->position];
    }
    
    public function key() {
        return $this->position;
    }
    
    public function next() {
        ++$this->position;
    }
    
    public function rewind() {
        $this->position = 0;
    }
    
    public function valid() {
        return isset($this->users[$this->position]);
    }
}

// Usage
$collection = new UserCollection();
$collection->addUser(new User('John'));
$collection->addUser(new User('Jane'));

// Works with count() function
echo count($collection); // 2

// Works with foreach
foreach ($collection as $user) {
    echo $user->getName();
}
                        

Homework: Building a WordPress Plugin with Interfaces

Assignment: Create a Simple Notification System

For this assignment, you'll create a simple WordPress plugin that sends notifications through different channels (email, admin notice, etc.) using interfaces to define the contract between components.

Requirements:

  1. Create a NotifierInterface with methods for sending and logging notifications
  2. Implement at least two notifier classes:
    • EmailNotifier: Sends notifications via email
    • AdminNotifier: Displays notifications as WordPress admin notices
  3. Create a NotificationManager class that can:
    • Register multiple notifiers
    • Send notifications through all registered notifiers or a specific notifier
    • Track notification history
  4. Add a simple admin page to test sending notifications
  5. Use proper type hinting and documentation throughout your code

Starter Code:


Bonus Challenges:

  • Add a SMSNotifier that simulates sending SMS notifications (you don't need to actually send SMS messages)
  • Create a NotificationFormatterInterface to allow different formatting strategies for different notification types
  • Add notification priority levels and the ability to filter notifications based on priority
  • Implement a DatabaseLogger that stores notification history in the WordPress database

Key Takeaways

Contract-Based Programming

Interfaces define contracts that implementing classes must fulfill, creating a clear separation between what a class can do and how it does it.

Multiple Implementation

Unlike abstract classes, a class can implement multiple interfaces, allowing for a form of multiple inheritance in PHP.

Decoupling and Flexibility

Using interfaces in type hints creates more flexible, decoupled code that can work with any class implementing the required interface.

SOLID Principles

Interfaces support key SOLID principles like Interface Segregation and Dependency Inversion, leading to more maintainable code.

WordPress Integration

Understanding interfaces helps you work with modern WordPress APIs and create more extensible plugins and themes.

Next Steps

Now that you understand interfaces, we'll continue exploring advanced OOP concepts with traits, which provide another way to reuse code across classes. Together, interfaces, abstract classes, and traits give you a powerful toolkit for building flexible, maintainable PHP applications and WordPress extensions.

Additional Resources