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: HelloThe 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
EasyLet'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 aStorableinterface with two method signatures:save($key, $data)andretrieve($key). This contract ensures any storage system can store and retrieve data, regardless of how it's implemented internally.FileStorage.php— Create aFileStorageclass that implementsStorable. Include the interface file. The class should have a private$directoryproperty set through the constructor. Implementsave($key, $data)to return"Saving '[data]' to file [directory]/[key].txt". Implementretrieve($key)to return"Reading from file [directory]/[key].txt".DatabaseStorage.php— Create aDatabaseStorageclass that also implementsStorable. Include the interface file. The class should have a private$tableNameproperty set through the constructor. Implementsave($key, $data)to return"Inserting '[data]' into table [tableName] with key [key]". Implementretrieve($key)to return"Selecting from table [tableName] where key = [key]".main.php— Include both storage files. Create a function calledstoreDatathat accepts aStorableparameter, a key, and data. The function should return the result of callingsave()with the key and data. Create another function calledfetchDatathat accepts aStorableparameter and a key, returning the result of callingretrieve().
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
?>This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe $this KeywordMethodsPropertiesConstructor (__construct)Destructor (__destruct)Recap - Simple Calculator4Inheritance
Basic InheritanceThe parent:: KeywordMethod OverridingThe final KeywordAbstract ClassesRecap - Employee Hierarchy7Encapsulation
Public, Protected, PrivateAccess Modifiers in DepthGetters and SettersInformation HidingConstructor Promotion (8.0)Recap - Student Records System2Namespaces & Autoloading
Introduction to NamespacesThe use KeywordPSR-4 Autoloading StandardComposer AutoloaderRecap - Organized Project5Interfaces & Contracts
Introduction to InterfacesImplementing InterfacesMultiple Interface ImplementInterface vs Abstract ClassType Hinting with InterfacesRecap - Shape Calculator8Magic Methods
Magic Methods Introduction__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Object Cloning__serialize & __unserializeRecap - Custom Collection11Type System & Error Handling
Type DeclarationsNullable TypesUnion & Intersection TypesException ClassesCustom Exception HierarchyTry, Catch, FinallyRecap - Form Validator14Project: Library Management
Project OverviewBook and User Classes3Class Properties
Instance vs Static PropertiesConstants in ClassesStatic Methods & PropertiesPrivate & Protected PropertiesReadonly Properties (PHP 8.1)Recap - Bank Account Manager6Polymorphism
Method Overriding RevisitedPolymorphism via InterfacesType Hinting & Union TypesLate Static BindingRecap - Payment Processor9Traits
Introduction to TraitsUsing Multiple TraitsTrait Conflict ResolutionAbstract Methods in TraitsTraits vs Inheritance12Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern