What is The Liskov Substitution Principle (in PHP)

When a developer extends a base class, the methods of that base class must be viewed with regard to its public behaviour. The developer extending the base class should assume that any other developer using their derived class does not have access to the internal workings of their code, but only the method signatures of the base class.

Based on those assumptions, we would be able to substitute any derived class for any other (where the base class is referenced) without any unexpected behaviour.

A violation of the open-closed principle

Let's look at a violation of the open-closed principle (OCP) and how the solution to it, is linked to the Liskov substitution principle.

Our Page class builds page elements using the buildElement method, but it violates OCP because it is not closed for change. If we wanted to build other element types, e.g.: fieldset, we would have to change the buildElement method.

So let's update our buildElement method to comply with the open-closed principle and then discuss how LSP fits in.

Our code now complies with OCP and LSP.

How do we comply with LSP?

The build method of the DivElement and SpanElement class both behave as we expect it to i.e we can pass a DivElement or a SpanElement to the buildElement method without any unexpected consequences.

We can substitute any derived class (like a future ParagraphElement) and the buildElement method will behave as we expect.

LSP with unexpected behaviour

If a developer were to guess the purpose of the buildElement method, we might guess that its purpose is to construct or put together the various pieces of the element i.e to get the element ready for rendering.

We wouldn't expect the buildElement method to behave differently depending on which derived class is passed to it.

To comply with the Liskov substitution principle, any new build method must not add or remove from the core purpose of what we reasonably assume the build method is. Suppose another developer built the ParagraphElement as shown below:

The above build method will fail when buildElement calls it, buildElement checks the properties before returning the output. But the build method of ParagraphElement sets the properties to null. It does not behave as we'd expect the base class method to behave.

In a similar way, if the build method were to comply with LSP, it must not remove the actual building functionality of it's build method (we do expect the build method to build something!)

LSP in Laravel

Let's look at an example of LSP in Laravel.

The Laravel example above has a base class/interface ConnectorInterface which is implemented by RedisConnector and DatabaseConnector. For our code to comply with LSP, any call to connect should work withs any derived class. In this case, the call to connect: $this->getConnector()->connect($config) does not check the type of the derived class, it is compliant with the Liskov substitution principle.

Key Takeaways

  • By implementing the Liskov substitution principle, developers can program to the base class or interface as they can be confident that all derived classes behave as they reasonably expect.
  • The Liskov substitution principle is violated when our reasonable assumptions of a method are either unmet or over exceeded i.e the method does too little or does more than what we expect.

Further Reading

This article is part of a series on SOLID in PHP, the previous article is The Open-Closed Principle.