L — SOLID Design Principle

Liskov Substitution Principle

Subtypes must be substitutable for their base type without altering the correctness of the program.

Problem

Square extends Rectangle because "a square is a rectangle" mathematically. But to stay square, setWidth() must also change height (and vice versa) — a side effect Rectangle never promised. Code written against Rectangle breaks silently when a Square is substituted in.

Solution

Stop forcing an inheritance relationship that requires lying about behaviour. Both Rectangle and Square implement a shared Shape interface independently, each honestly fulfilling only the contract it can actually keep.

Analogy

A remote-control toy car that's "a subtype of Car" but the steering wheel doesn't work — technically a car, but you can't drive it like every other car. If a subtype changes the rules, it isn't really substitutable.

Participants (After)

Class Role
ShapeThe abstraction — only promises area()
RectangleIndependent width/height, implements Shape directly
SquareOne side length, implements Shape directly — no longer extends Rectangle

Run the Demo

Activity Log

Press a button above to see the pattern in action.

Source Code

Before: Rectangle.php
<?php

declare(strict_types=1);

namespace App\SolidPrinciples\Lsp\Before;

/**
 * LSP VIOLATION SETUP — an ordinary rectangle with independent
 * width/height setters. Any code using Rectangle expects that
 * setting width does NOT change height, and vice versa.
 */
class Rectangle
{
    protected float $width = 0;
    protected float $height = 0;

    public function setWidth(float $width): void
    {
        $this->width = $width;
    }

    public function setHeight(float $height): void
    {
        $this->height = $height;
    }

    public function getWidth(): float
    {
        return $this->width;
    }

    public function getHeight(): float
    {
        return $this->height;
    }

    public function area(): float
    {
        return $this->width * $this->height;
    }
}
Before: Square.php
<?php

declare(strict_types=1);

namespace App\SolidPrinciples\Lsp\Before;

/**
 * LSP VIOLATION — mathematically a square IS a rectangle, so it seems
 * natural to extend Rectangle. But to keep a square "square", setting
 * width must also change height (and vice versa) — silently breaking
 * the parent's contract. Any code written against Rectangle that
 * substitutes a Square here gets surprising, wrong behaviour.
 */
class Square extends Rectangle
{
    public function setWidth(float $width): void
    {
        $this->width = $width;
        $this->height = $width; // side-effect the base class never promised
    }

    public function setHeight(float $height): void
    {
        $this->width = $height; // side-effect the base class never promised
        $this->height = $height;
    }
}
After: Shape.php
<?php

declare(strict_types=1);

namespace App\SolidPrinciples\Lsp\After;

/**
 * LSP FIX — a common abstraction that makes no promises about
 * width/height independence. Both Rectangle and Square honestly
 * implement only what they can guarantee: an area.
 */
interface Shape
{
    public function area(): float;
}
After: Rectangle.php
<?php

declare(strict_types=1);

namespace App\SolidPrinciples\Lsp\After;

/**
 * LSP FIX — Rectangle still has independent width/height, but it no
 * longer sits in an inheritance relationship that a Square must lie
 * about to fit into.
 */
class Rectangle implements Shape
{
    public function __construct(
        private float $width,
        private float $height,
    ) {}

    public function area(): float
    {
        return $this->width * $this->height;
    }
}
After: Square.php
<?php

declare(strict_types=1);

namespace App\SolidPrinciples\Lsp\After;

/**
 * LSP FIX — Square is its OWN class implementing Shape directly. It
 * makes an honest, self-contained promise: one side length, one area.
 * It no longer extends Rectangle, so it can never violate Rectangle's
 * contract — because it never inherited that contract in the first place.
 */
class Square implements Shape
{
    public function __construct(
        private float $side,
    ) {}

    public function area(): float
    {
        return $this->side * $this->side;
    }
}