PHP Course : SOLID - Single Responsibility

PHP Course : SOLID - Single Responsibility


Jan 24 2019, 12:54 in Web Development

Have you ever heard about SOLID? It is a collection of several Programming Principles that help developers write better code.

There are five letters in SOLID, and each stand for a specific principle. In this article we will start with the first one "S".

S stands for Single Responsibility principle.

This principle dictates that a class should have only one reason to change. You probably already know this one, and it is likely that' you're already implementing it in some degree.

Let's dive into some examples.

Let's imagine we have a OrderController class like so

class OrderReporter
{
    public function between($startDate, $endDate)
    {
        // Check Authentication
        if (!auth()->check()) {
            throw new Exception('You must be authenticated');
        }

        // Get Orders from Database
        $amount = $this->getOrdersFromDatabaseBetween($startDate, $endDate);

        // return formatted result
        return $this->format($amount);
    }

    protected function getOrdersFromDatabaseBetween($startDate, $endDate)
    {
        return DB::table('orders')->whereBetween('created_at', [$startDate, $endDate])->sum('amount');
    }

    protected function format($amount)
    {
        return 'Total: ' . $amount;
    }
}

A bit of a weird piece of code, but it's just to illustrate the refactoring.

Indeed the code is working fine and you can use it without any issue, but it is not really adapted if you have to change things. And that's what Single Responsability is all about. Making it easy to operate changes in your classes.

Let's make it more clear:

An object should have only one reason to change. A class should have only one job. A class should have a Single Responsability.

If it doesn't, then you probably need to extract some of the code into their own classes.

Taking this into account, let's see how to make our code better so it fits this definition.

The first thing we can do is to move the authentication check out of this class. Why ? Because my OrderReporter class has no interest at all knowing about the users and the permission. Another class should worry about that. So we can extract this part and move it to the controller, or better, to a middleware or a policy (see Laravel documentation for this).

Next, we see that we are doing 2 different things here: we query the database and fetch some entries. Then we format the result. You see, we are doing two different things. If we want to change the way the result is formatted, you will need to change the OrderReporter class. And if you need to change the Database query, you also need to update the OrderReporter class. This is a strong sign that you're violating the Single Responsability Principle.

Alright, we will first move the code responsible for querying the database in its own class. Typically in this case you will need a Repository Class or something similar:

class OrderReporter
{
    protected $repository;

    public function __construct(OrderRepository $repository)
    {
        $this->repository = $repository;
    }

    public function between($startDate, $endDate)
    {
        // Get Orders from Database
        $amount = $this->repository->between($startDate, $endDate);

        // return formatted result
        return $this->format($amount);
    }
    
    protected function format($amount)
    {
        return 'Total: ' . $amount;
    }
}

Finally, we can tackle the format method.

Again, let's ask the question why does OrderReporter should care about formatting at all?. Or even Is it possible that you are going to change the way the result is formatted in the future? What if you want your results in JSON instead?

Those questions are valid and are a great indicator that you need to refactor some code.

One valid solution for this case is to let the user care about formatting. You can simply return the $result from the repository, and remove the formatting part entirely.

Another great solution is to create several classes each responsible for an formatting method, and inject this as a dependency.

We can for example create a OrderFormatInterface and inject it into OrderReporter like so:

class OrderReporter
{
    protected $repository;

    public function __construct(OrderRepository $repository)
    {
        $this->repository = $repository;
    }

    public function between($startDate, $endDate, OrderFormatInterface $format)
    {
        // Get Orders from Database
        $amount = $this->repository->between($startDate, $endDate);

        // return formatted result
        return $format->output($amount);
    }
}

We now have a very clean Class that only takes care of one thing only. Responsability is splitted and each class are responsible of one thing only.

I hope it will help you understand better this first principle!


Share:
Like:

Disable AdBlock on this domain and offer me a cup of coffee :)