3. Liskov Principle - mrbahadoor/symfony5-solid GitHub Wiki

Background
Developed by Barbara Liskov, computer scientist and winner of Turing award(like nobel prize for IT).

The Liskov Substitution Principle (LSP) is one of the SOLID principles of object-oriented programming. It states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In other words, a subclass should be substitutable for its superclass without breaking the behavior expected by clients of the superclass.

Generic definition
Subtypes(any class that extends a base class or implements an interface) must be substituable for their base types (abstract / parent classes or interfaces).

Simplified definition
You should be able to substitute a class for a sub-class without breaking you app.

Rules

  1. You cannot change / override the type of a protected property:
    Example: If parent class has protected int $quantity, in child class protected float $quantity cannot be set.

  2. You cannot narrow (make more specific) the type of an argument (type hint) in a subtype, for a method that lies in the base type:
    Example: If parent class has public function calucateScore(UserInterface $user), in child class public function calculateScore(User $user) is not allowed. The inverse is allowed.

  3. You cannot widen (make less specific) the return type of a method (return type), in a subtype, for a method that lies in the base type:
    Example: If parent class has public function calculateScore(Object $score): childObject, in child class public function calculateScore(Object $score): parentObject is not allowed. The inverse is allowed.

  4. Follow your parent type's rules on when and why to throw exceptions

Note: Rules 1 to 3 are impossible to violate in php.

Class Substitution
To substitute a service sub-class for auto-wiring, use class key in services.yaml:
App\Service\SightingScorer:
class: App\Service\DebuggableSightingScorer
argugments:
$scoringFactors: !tagged_iterator scoring.factor

In subclass:
class DebuggableSightingScorer extends SightingScorer
{
public function score(BigFootSighting $sighting): BigFootSightingScore
{
return parent::score($sighting);
}
}

LSP for Interfaces: When a class implements an interface, it's essentially promising to fulfill the contract specified by that interface. Therefore, the class should be substitutable for any other class implementing the same interface, without altering the expected behavior of the program.

image
Thus, even though both classes adhere to the interface contract by implementing the eat() and fly() methods, the Liskov Substitution Principle reminds us that a Penguin should be substitutable for a Bird, which includes the ability to fly, but it doesn't behave as expected.

To adhere to LSP, one could consider redefining the interface to be more flexible or use different interfaces that define subsets of behaviors that are cohesive, adhering to the Interface Segregation Principle.

Changes allowed

  1. Narrower (subclasses) return types are allowed.
  2. Wider argument types (parent / abstract / interface) are allowed.

Consideration
For a client class (Controller, Manager or Service) that depends on an interface (base type), the subtype(classes that implements interfaces) are allowed in the __constructor() method.

⚠️ **GitHub.com Fallback** ⚠️