Open for Extension, Closed for Modification
The Open/Closed Principle says: code should be open for extension — new behavior can be added — but closed for modification — existing, working, tested code is not edited for it.
Every edit to working code is a chance to break it. When adding a feature means only adding new code, the old code stays proven.
PHP's tool for this is the interface: a function that depends on an interface never needs to change when a new class implements it.
The switch That Never Stops Growing
This area calculator uses a switch on the shape kind. It works — until the next shape arrives. Triangle? Edit the switch. Pentagon? Edit it again. The function is never finished, and every edit risks the shapes that already worked.
A switch on the shape kind
totalArea must know every kind of shape. Adding one means modifying this tested function — the exact thing the principle warns against.
Output: PHP script
<?php
class Shape {
public function __construct(
public string $kind,
public float $width = 0,
public float $height = 0,
public float $radius = 0,
) {
}
}
// totalArea must know every kind — and grows with each new one.
function totalArea(Shape ...$shapes): float {
$total = 0.0;
foreach ($shapes as $s) {
switch ($s->kind) {
case "circle":
$total += M_PI * $s->radius * $s->radius;
break;
case "rectangle":
$total += $s->width * $s->height;
break;
}
}
return $total;
}
$total = totalArea(
new Shape(kind: "circle", radius: 2),
new Shape(kind: "rectangle", width: 3, height: 4),
);
printf("total area: %.2f\n", $total);
Extend Through an Interface
The fix: each shape knows its own area. The Shape interface asks for one method, area(), and totalArea just adds up whatever it receives — it no longer cares what the shapes are.
Now watch the principle at work: a brand-new Triangle plugs in, and totalArea is not touched. Extension without modification.
Each shape computes its own area
totalArea depends only on the Shape interface. The switch is gone — and with it, the need to ever edit this function again.
Output: PHP script
<?php
interface Shape {
public function area(): float;
}
class Circle implements Shape {
public function __construct(public float $radius) {
}
public function area(): float {
return M_PI * $this->radius * $this->radius;
}
}
class Rectangle implements Shape {
public function __construct(public float $width, public float $height) {
}
public function area(): float {
return $this->width * $this->height;
}
}
// totalArea depends only on the interface — no switch, nothing to edit.
function totalArea(Shape ...$shapes): float {
$total = 0.0;
foreach ($shapes as $s) {
$total += $s->area();
}
return $total;
}
$total = totalArea(
new Circle(radius: 2),
new Rectangle(width: 3, height: 4),
);
printf("total area: %.2f\n", $total);
Add a new shape, touch nothing
Triangle is pure new code: a class that implements area(). totalArea, Circle and Rectangle are exactly as they were — open for extension, closed for modification.
Output: PHP script
<?php
interface Shape {
public function area(): float;
}
class Circle implements Shape {
public function __construct(public float $radius) {
}
public function area(): float {
return M_PI * $this->radius * $this->radius;
}
}
class Rectangle implements Shape {
public function __construct(public float $width, public float $height) {
}
public function area(): float {
return $this->width * $this->height;
}
}
// Triangle is NEW code — everything above is untouched.
class Triangle implements Shape {
public function __construct(public float $base, public float $height) {
}
public function area(): float {
return $this->base * $this->height / 2;
}
}
function totalArea(Shape ...$shapes): float {
$total = 0.0;
foreach ($shapes as $s) {
$total += $s->area();
}
return $total;
}
$total = totalArea(
new Circle(radius: 2),
new Rectangle(width: 3, height: 4),
new Triangle(base: 6, height: 5),
);
printf("total area: %.2f\n", $total);