Keep the Promise
The Liskov Substitution Principle says: anywhere the code expects an interface, every class that implements it must work correctly. An implementation that technically fits but behaves badly — throws, does nothing, lies — breaks every function that trusted the interface.
An interface is a promise. fly() promises flying. A class that implements fly() by throwing an exception has signed a contract it cannot keep.
A Substitute That Breaks the Deal
The Bird interface promises fly(). Sparrows deliver. Penguins are birds too — so it feels natural to implement the interface... with a throw, because penguins cannot fly.
Now airShow, which works for every honest bird, crashes on a penguin. The bug is not in airShow — it is in the substitute that broke the promise.
The penguin that crashes the show
airShow trusts the interface and calls fly() on every bird. The sparrow performs; the penguin throws an exception and takes the whole show down (the try/catch only softens the landing to show the message).
Output: PHP script
<?php
interface Bird {
public function fly(): string;
}
class Sparrow implements Bird {
public function fly(): string {
return "sparrow: flying low";
}
}
// Penguin signs the contract — but cannot keep it.
class Penguin implements Bird {
public function fly(): string {
throw new RuntimeException("penguins cannot fly");
}
}
function airShow(Bird ...$birds): void {
foreach ($birds as $b) {
echo $b->fly(), "\n";
}
}
try {
airShow(new Sparrow(), new Penguin());
} catch (RuntimeException $e) {
echo "the show crashed: ", $e->getMessage(), "\n";
}
Separate the Promises
The fix: promise only what is true. Split the fat promise into Flyer and Swimmer. The sparrow implements Flyer, the penguin implements Swimmer — and no class signs a contract it cannot keep.
The best part is who catches the mistake now: airShow type-hints Flyer, so airShow(new Penguin()) fails the type check — PHP stops the call with a TypeError before any bird performs, and a static analyzer reports the line before the code even runs.
Every class keeps its promise
airShow accepts Flyer, poolShow accepts Swimmer. Each animal appears only where its promise holds — substitution is safe by construction.
Output: PHP script
<?php
interface Flyer {
public function fly(): string;
}
interface Swimmer {
public function swim(): string;
}
class Sparrow implements Flyer {
public function fly(): string {
return "sparrow: flying low";
}
}
class Penguin implements Swimmer {
public function swim(): string {
return "penguin: diving deep";
}
}
function airShow(Flyer ...$flyers): void {
foreach ($flyers as $f) {
echo $f->fly(), "\n";
}
}
function poolShow(Swimmer ...$swimmers): void {
foreach ($swimmers as $s) {
echo $s->swim(), "\n";
}
}
airShow(new Sparrow());
poolShow(new Penguin());
// airShow(new Penguin()) would fail the type check with a TypeError.
Any Flyer can join
The payoff of honest interfaces: a new Eagle joins the air show with no risk — anything that implements Flyer truly flies, so airShow never needs to doubt its input.
Output: PHP script
<?php
interface Flyer {
public function fly(): string;
}
class Sparrow implements Flyer {
public function fly(): string {
return "sparrow: flying low";
}
}
// Eagle is new — and it truly flies, so it substitutes safely.
class Eagle implements Flyer {
public function fly(): string {
return "eagle: soaring high";
}
}
function airShow(Flyer ...$flyers): void {
foreach ($flyers as $f) {
echo $f->fly(), "\n";
}
}
airShow(new Sparrow(), new Eagle());