Menu
Coddy logo textTech

Polymorphism via Interfaces

Part of the Object Oriented Programming section of Coddy's PHP journey — lesson 34 of 91.

In the previous lesson, you saw polymorphism through inheritance, where child classes override parent methods. Interfaces provide another powerful way to achieve polymorphism, especially when working with unrelated classes that share common behavior.

With interfaces, polymorphism comes from the contract itself. Any class implementing an interface guarantees it has certain methods, regardless of its inheritance hierarchy:

<?php
interface Notifiable {
    public function send(string $message);
}

class EmailNotifier implements Notifiable {
    public function send(string $message) {
        return "Email: " . $message;
    }
}

class SMSNotifier implements Notifiable {
    public function send(string $message) {
        return "SMS: " . $message;
    }
}

class PushNotifier implements Notifiable {
    public function send(string $message) {
        return "Push: " . $message;
    }
}

function notify(Notifiable $notifier, string $message) {
    return $notifier->send($message);
}

echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");

Output:

Email: Hello
SMS: Hello
Push: Hello

The notify() function works with any Notifiable object. These classes don't share a parent class - they're completely unrelated except for implementing the same interface. This is the key advantage over inheritance-based polymorphism: you can group unrelated classes by their capabilities rather than their ancestry.

Key Point: Interface-based polymorphism lets unrelated classes be used interchangeably based on shared behavior, making your code more flexible and decoupled from specific implementations.

challenge icon

Challenge

Easy

Let's build a storage system that demonstrates interface-based polymorphism. You'll create different storage backends — a file system and a database — that have nothing in common except they both implement the same storage interface. A single function will work with any storage type, showcasing how interfaces enable polymorphism between completely unrelated classes.

You'll organize your code across four files:

  • Storable.php — Define a Storable interface with two method signatures: save($key, $data) and retrieve($key). This contract ensures any storage system can store and retrieve data, regardless of how it's implemented internally.
  • FileStorage.php — Create a FileStorage class that implements Storable. Include the interface file. The class should have a private $directory property set through the constructor. Implement save($key, $data) to return "Saving '[data]' to file [directory]/[key].txt". Implement retrieve($key) to return "Reading from file [directory]/[key].txt".
  • DatabaseStorage.php — Create a DatabaseStorage class that also implements Storable. Include the interface file. The class should have a private $tableName property set through the constructor. Implement save($key, $data) to return "Inserting '[data]' into table [tableName] with key [key]". Implement retrieve($key) to return "Selecting from table [tableName] where key = [key]".
  • main.php — Include both storage files. Create a function called storeData that accepts a Storable parameter, a key, and data. The function should return the result of calling save() with the key and data. Create another function called fetchData that accepts a Storable parameter and a key, returning the result of calling retrieve().

You'll receive four inputs: a directory path, a table name, a key, and some data to store. Create a FileStorage with the directory and a DatabaseStorage with the table name. Use your storeData() function to save the data using the file storage first, then the database storage. Print each result on its own line. Then use fetchData() to retrieve from the file storage and print that result.

Notice how storeData() and fetchData() work identically with both storage types — they only care that the object implements Storable. The file system and database have completely different internal implementations, yet they're interchangeable through the interface contract.

Cheat sheet

Interfaces enable polymorphism by allowing unrelated classes to be used interchangeably based on shared behavior rather than inheritance hierarchy.

Any class implementing an interface guarantees it has certain methods, creating a contract that enables polymorphic behavior:

<?php
interface Notifiable {
    public function send(string $message);
}

class EmailNotifier implements Notifiable {
    public function send(string $message) {
        return "Email: " . $message;
    }
}

class SMSNotifier implements Notifiable {
    public function send(string $message) {
        return "SMS: " . $message;
    }
}

function notify(Notifiable $notifier, string $message) {
    return $notifier->send($message);
}

echo notify(new EmailNotifier(), "Hello");
echo notify(new SMSNotifier(), "Hello");

The notify() function accepts any object implementing Notifiable, regardless of the class hierarchy. This makes code more flexible and decoupled from specific implementations.

Key advantage: Interface-based polymorphism groups unrelated classes by their capabilities, not their ancestry, allowing completely different implementations to be used interchangeably through a common contract.

Try it yourself

<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';

// Read inputs
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));

// TODO: Create a function storeData that accepts a Storable, key, and data
// It should return the result of calling save() with the key and data

// TODO: Create a function fetchData that accepts a Storable and key
// It should return the result of calling retrieve() with the key

// TODO: Create a FileStorage instance with the directory

// TODO: Create a DatabaseStorage instance with the table name

// TODO: Use storeData() to save data with file storage, then print the result

// TODO: Use storeData() to save data with database storage, then print the result

// TODO: Use fetchData() to retrieve from file storage, then print the result
?>
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming