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
ShapableThe abstraction — declares area()
Square / Triangle / CircleExisting concrete shapes
PentagonNEW shape — proves extension without modification
AreaCalculatorNever 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();
    }
}