blog

SOLID Principles (PHP)

Understanding SOLID principles

What is the SOLID-principles?
According to Wikipedia’s definition it’s abbreviation of the five basic principles of design classes in object-oriented design:

Single responsibility
Open-closed
Liskov substitution
Interface segregation
Dependency inversion

1. Single Responsibility

So, as an example lets take popular and widely-used example – an online store with orders, products and customers. The principle states that the only responsibility – “one single duty should be imposed on each object.” In other words – a specific class must solve a specific task – no more and no less. Consider the following description of the class to represent the order in the online store:

Sample 1. Class Order

<?php
class Order
{
    public function calculateTotalSum(){/*...*/}
    public function getItems(){/*...*/}
    public function getItemCount(){/*...*/}
    public function addItem($item){/*...*/}
    public function deleteItem($item){/*...*/}

    public function printOrder(){/*...*/}
    public function showOrder(){/*...*/}

    public function load(){/*...*/}
    public function save(){/*...*/}
    public function update(){/*...*/}
    public function delete(){/*...*/}
}
?>

As you can see, this class performs the operation for 3 different types of tasks: work with every order (calculateTotalSum, getItems, getItemsCount, addItem, deleteItem), display order (printOrder, showOrder) and data handeling (load, save, update, delete).

What does it lead to?

This leads to the case that if we want to make changes to the print job, or storage techniques, we change the order class itself, which may lead to inoperability. To solve this problem is the division of the class into 3 classes, each of which will be to carry out their task:

Sample 2. Class Order split into 3 classes

<?php
class Order
{
    public function calculateTotalSum(){/*...*/}
    public function getItems(){/*...*/}
    public function getItemCount(){/*...*/}
    public function addItem($item){/*...*/}
    public function deleteItem($item){/*...*/}
}

class OrderRepository
{
    public function load($orderID){/*...*/}
    public function save($order){/*...*/}
    public function update($order){/*...*/}
    public function delete($order){/*...*/}
}

class OrderViewer
{
    public function printOrder($order){/*...*/}
    public function showOrder($order){/*...*/}
}
?>

Now each class is engaged in the specific task and for each class there is only one reason to change it.

2. Open-Closed Principle

This principle declares that – “software entities should be open for extension, but closed for modification.” In more simple words it can be described as – all classes, functions, etc. should be designed so that to change their behavior, we do not need to modify their source code.

Consider the example of OrderRepository class.

Sample 3. Class OrderRepository

<?php
class OrderRepository
{
    public function load($orderID)
    {
        $pdo = new PDO(
            $this->config->getDsn(),
            $this->config->getDBUser(),
            $this->config->getDBPassword()
        );
        $statement = $pdo->prepare("SELECT * FROM `orders` WHERE id=:id");
        $statement->execute(array(":id" => $orderID));
        return $query->fetchObject("Order");
    }
    
    public function save($order){/*...*/}
    public function update($order){/*...*/}
    public function delete($order){/*...*/}
}
?>

In this case, we have a repository database, for example: MySQL. But suddenly we want to load our data on orders via API of the third-party server.

What changes do we need to make? There are several options, for example: to directly modify class methods OrderRepository, but this does not comply with the principle of opening / closing, since the class is closed to modifications, and changes to the already well working class is not desirable. So, you can inherit from OrderRepository class and override all the methods, but this solution is also not the best, because when you add a method to OrderRepository we have to add similar methods to all his successors. Therefore, to satisfy the principle of opening / closing is better to use the following solution – to establish interface IOrderSource, which will be implemented by the respective classes MySQLOrderSource, ApiOrderSource and so on.

Sample 4. IOrderSource interface and its implementation and use

<?php
class OrderRepository
{
    private $source;

    public function setSource(IOrderSource $source)
    {
        $this->source = $source;
    }

    public function load($orderID)
    {
        return $this->source->load($orderID);
    }
    
    public function save($order){/*...*/}
    public function update($order){/*...*/}
}

interface IOrderSource
{
    public function load($orderID);
    public function save($order);
    public function update($order);
    public function delete($order);
}

class MySQLOrderSource implements IOrderSource
{
    public function load($orderID);
    public function save($order){/*...*/}
    public function update($order){/*...*/}
    public function delete($order){/*...*/}
}

class ApiOrderSource implements IOrderSource
{
    public function load($orderID);
    public function save($order){/*...*/}
    public function update($order){/*...*/}
    public function delete($order){/*...*/}
}
?>

Thus, we can change the behavior of the source and accordingly to OrderRepository class, setting us right class implements IOrderSource, without changing OrderRepository class.

3. The Substitution Principle (Liskov Substitution)

Perhaps, the principle that causes the greatest difficulties in understanding. The principle says – “Objects in the program can be replaced by their heirs without changing the properties of the program.” In my words, I would say so – when using the class heir, the result of the code execution should be predictable and do not change the properties of the method. There is a classic example with a hierarchy of geometric shapes and area calculations. The example of code is below.

Sample 5. Hierarchy of a rectangle and a square and calculating their area.

<?php
class Rectangle
{
    protected $width;
    protected $height;

    public setWidth($width)
    {
        $this->width = $width;
    }

    public setHeight($height)
    {
        $this->height = $height;
    }

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

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

class Square extends Rectangle
{
    public setWidth($width)
    {
        parent::setWidth($width);
        parent::setHeight($width);
    }

    public setHeight($height)
    {
        parent::setHeight($height);
        parent::setWidth($height);
    }
}

function calculateRectangleSquare(Rectangle $rectangle, $width, $height)
{
    $rectangle->setWidth($width);
    $rectangle->setHeight($height);
    return $rectangle->getHeight * $rectangle->getWidth;
}

calculateRectangleSquare(new Rectangle(), 4, 5); // 20
calculateRectangleSquare(new Square(), 4, 5); // 25 ???
?>

Obviously, such code is not executed as expected. But what’s the problem? Is a “square” is not a “rectangle”? Yes, but in geometric terms. In terms of the same objects, the square is not a rectangle, because the behavior of the “square” object does not agree with the behavior of the “rectangle” object.

Ok, so how to solve the problem? The solution is closely related to the notion of contract design. The description of designing under the contract can take not one article, therefore we will be limited to features which concern the Liskov principle. Contract design leads to some limitations on how contracts can interact with inheritance, namely:

  • Preconditions can not be strengthened in a subclass.
  • Postconditions can not be weakened in a subclass.

4. The principle of interface separation (Interface segregation)

This principle says that “Many specialized interfaces are better than one universal”. Compliance with this principle is necessary to ensure that the client classes that use or implement the interface will know only about the methods that they use, which leads to a reduction in the amount of unused code.

Let’s take an example with an online store. Suppose our products can have a promotional code, a discount, they have some price, condition, etc. If this is clothing, then for it it is determined from what material is made, color and size. Let’s describe the following interface:

Sample 6. IItem interface.

<?php
interface IItem
{
    public function applyDiscount($discount);
    public function applyPromocode($promocode);

    public function setColor($color);
    public function setSize($size);
    
    public function setCondition($condition);
    public function setPrice($price);
}
?>

This interface is not good, because it involves too many methods. And what if our class of goods can not have discounts or promotional codes, or for it there is no sense in installing the material from which it is made (for example, for books). Thus, in order not to implement methods that are not used in each class, it is better to break the interface into several smaller ones and implement the necessary interfaces by each specific class.

Sample 7. Splitting the IItem interface into several interfaces.

<?php
interface IItem
{
    public function setCondition($condition);
    public function setPrice($price);
}

interface IClothes
{
    public function setColor($color);
    public function setSize($size);
    public function setMaterial($material);
}

interface IDiscountable
{
    public function applyDiscount($discount);
    public function applyPromocode($promocode);
}

class Book implemets IItem, IDiscountable
{
    public function setCondition($condition){/*...*/}
    public function setPrice($price){/*...*/}
    public function applyDiscount($discount){/*...*/}
    public function applyPromocode($promocode){/*...*/}
}

class KidsClothes implemets IItem, IClothes
{
    public function setCondition($condition){/*...*/}
    public function setPrice($price){/*...*/}
    public function setColor($color){/*...*/}
    public function setSize($size){/*...*/}
    public function setMaterial($material){/*...*/}
}
?>

5. Principle of Dependency Inversion

This principle says – “Dependencies within the system are built on the basis of abstractions. The top-level modules do not depend on the lower-level modules. Abstractions should not depend on details. Details must depend on abstractions.” This definition can be shortened – “the dependencies should be based on abstractions, not details.”

For example, consider the payment of the order by the buyer.

Sample 8. Payment of the order by the buyer.

<?php
class Customer
{
    private $currentOrder = null;

    public function buyItems()
    {    
        if(is_null($this->currentOrder)){
            return false;
        }
        $processor = new OrderProcessor();
        return $processor->checkout($this->currentOrder);    
    }

    public function addItem($item){
        if(is_null($this->currentOrder)){
            $this->currentOrder = new Order();
        }
        return $this->currentOrder->addItem($item);
    }
    
    public function deleteItem($item){
        if(is_null($this->currentOrder)){
            return false;
        }
        return $this->currentOrder ->deleteItem($item);
    }
}

class OrderProcessor
{
    public function checkout($order){/*...*/}
}
?>

Everything seems quite logical. But there is a one problem – the Customer class depends on the OrderProcessor class (moreover, the principle of openness/closure is not fulfilled). In order to get rid of the dependence on a particular class, you need to make sure that Customer depends on abstraction, ie. From the IOrderProcessor interface. This dependency can be implemented through the setters, method parameters, or the Dependency Injection container. I decided to stop on method 2 and get the following code.

Sample 9. Inverting dependency of the Customer class.

<?php
class Customer
{
    private $currentOrder = null;

    public function buyItems(IOrderProcessor $processor)
    {    
        if(is_null($this->currentOrder)){
            return false;
        }
        
        return $processor->checkout($this->currentOrder);    
    }

    public function addItem($item){
        if(is_null($this->currentOrder)){
            $this->currentOrder = new Order();
        }
        return $this->currentOrder->addItem($item);
    }
    public function deleteItem($item){
        if(is_null($this->currentOrder)){
            return false;
        }
        return $this->currentOrder ->deleteItem($item);
    }
}

interface IOrderProcessor
{
    public function checkout($order);
}

class OrderProcessor implements IOrderProcessor
{
    public function checkout($order){/*...*/}
}
?>

So now, the Customer class now depends only on the abstraction, and the specific implementation, i.e. details, it is not so important.

Conclusion

Summarizing all of the above, I would like to make the following cheat sheet

  • The principle of a single responsibility
    “One object must be assigned to each facility”
    To do this, we check how many reasons we have for changing the class-if there is more than one, then we must break this class.
  • The principle of open-closedness
    “Software entities must be open for expansion, but they are closed for modification”
    For this, we represent our class as a “black box” and see if we can change its behavior in this case.
  • The substitution principle of Liskov substitution
    “Objects in the program can be replaced by their heirs without changing the properties of the program”
    For this, we check whether we have strengthened the preconditions and whether the postcondition has weakened. If this happened, then the principle is not observed
  • The principle of interface separation (Interface segregation)
    “Many specialized interfaces are better than one universal”
    We check how much the interface contains methods and how different functions are superimposed on these methods, and if necessary, we break the interfaces.
  • The principle of Dependency Invertion
    “Dependencies should be built on abstractions, not details”
    We check whether the classes depend on some other classes (instantly instantiate objects of other classes, etc.) and if this relationship takes place, we replace it with a dependence on abstraction.

 

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Leave a Reply