SOLID Principles with PHP examples

This tutorial describes and shows SOLID principles with examples in PHP.
Provided by Leumas Naypoka / www.apphp.com

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:

<?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:

<?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.

<?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.

<?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.



To be continued...

Comments


Please post only comments related to the original tutorial. Be polite and helpful, do not spam or offend others.
Create Your Free Account
Please remember that this information is essential to use our services correctly.
After creating the account you will be able to download all of our FREE products.
Fields marked with * are mandatory






Please send me information about updates, new products, specials and discounts from ApPHP!
We recommend that your password should be at least 6 characters long and should be different from your username/email. Please use only letters of the English alphabet to enter your name.

Your e-mail address must be valid. We use e-mail for communication purposes (order notifications, etc). Therefore, it is essential to provide a valid e-mail address to be able to use our services correctly.

All your private data is confidential. We will never sell, exchange or market it in any way. Please refer to Privacy Policy.

By clicking "Create Account", you are indicating that you have read and agree to the ApPHP Terms & Conditions.

Quick Registration with: Facebook / Google