O — SOLID
Design Principle
Open/Closed Principle
Software entities should be open for extension, but closed for modification.
Problem
AreaCalculator uses an if/elseif chain to handle each shape type. Every new shape (Pentagon, Hexagon...) means opening this class and adding another branch — risking breaking Square, Triangle, and Circle in the process.
Solution
Extract a Shapable interface with an area() method. AreaCalculator depends only on that interface. New shapes are added by creating a new class that implements Shapable — AreaCalculator.php is never touched again.
Analogy
A power socket doesn't need rewiring every time a new appliance is invented — any plug that fits the standard socket shape just works. The socket is closed for modification, open for extension.
Participants (After)
| Class | Role |
|---|---|
| Shapable | The abstraction — declares area() |
| Square / Triangle / Circle | Existing concrete shapes |
| Pentagon | NEW shape — proves extension without modification |
| AreaCalculator | Never changes — depends only on Shapable |
Run the Demo
Activity Log
Press a button above to see the pattern in action.
Source Code
▸ Before: AreaCalculator.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Ocp\Before;
/**
* OCP VIOLATION — AreaCalculator must be MODIFIED every time a new shape
* is added. The if/elseif chain grows forever, and every edit risks
* breaking the shapes that already work.
*/
class AreaCalculator
{
/**
* @param array{type:string, width?:float, height?:float, base?:float, radius?:float} $shape
*/
public function calculate(array $shape): float
{
if ($shape['type'] === 'square') {
return $shape['width'] * $shape['height'];
} elseif ($shape['type'] === 'triangle') {
return $shape['height'] * $shape['base'] / 2;
} elseif ($shape['type'] === 'circle') {
return $shape['radius'] * $shape['radius'] * pi();
}
// Adding a Rectangle, Pentagon, etc. means opening THIS method
// and adding another elseif branch — closed for modification
// is violated every single time.
throw new \RuntimeException('Unknown shape type: ' . $shape['type']);
}
}
▸ After: Shapable.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Ocp\After;
/**
* OCP FIX — the abstraction every shape implements.
* AreaCalculator depends only on this interface, never on concrete shapes.
*/
interface Shapable
{
public function area(): float;
}
▸ After: Square.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Ocp\After;
class Square implements Shapable
{
public function __construct(
private float $width,
private float $height,
) {}
public function area(): float
{
return $this->width * $this->height;
}
}
▸ After: Triangle.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Ocp\After;
class Triangle implements Shapable
{
public function __construct(
private float $height,
private float $base,
) {}
public function area(): float
{
return $this->height * $this->base / 2;
}
}
▸ After: Circle.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Ocp\After;
class Circle implements Shapable
{
public function __construct(
private float $radius,
) {}
public function area(): float
{
return $this->radius * $this->radius * pi();
}
}
▸ After: Pentagon.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Ocp\After;
/**
* OCP FIX PROOF — a brand new shape, added WITHOUT touching
* AreaCalculator or any existing shape class. This is "open for
* extension": Pentagon simply implements Shapable and it just works.
*/
class Pentagon implements Shapable
{
public function __construct(
private float $sideLength,
) {}
public function area(): float
{
// Regular pentagon area formula: (5 * s^2) / (4 * tan(pi/5))
return (5 * $this->sideLength ** 2) / (4 * tan(pi() / 5));
}
}
▸ After: AreaCalculator.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Ocp\After;
/**
* OCP FIX — this class never changes again, no matter how many new
* shapes are added. It is CLOSED for modification but OPEN for
* extension via the Shapable interface.
*/
class AreaCalculator
{
public function calculate(Shapable $shape): float
{
return $shape->area();
}
}