The Single Responsibility Principle

Share this article

Robust software systems should be built up from a web of interrelated objects whose responsibilities are tight, neatly cohesive, boiled down to just performing a few narrowed and well-defined tasks. However, it’s admittedly pretty difficult to design such systems, at least in the first take. Most of the time we tend to group tasks in question by following an ad-hoc semantic sense in consonance with the nature of our own human mind. One of the most notorious consequences of this rational associative process is that, at some point, we effectively end up creating classes that do too much. The so-called “God class” is quite possibly the most extreme and coarse example of a structure that packages literally piles of unrelated operations behind the fence of the same API, but there are other subtle, more furtive situations where assigning of multiple roles to the same class are harder to track down. A good example of this is the Singleton. We all know that Singletons have been condemned for years because they suffer all sort of nasty implementation issues, with the classic mutable global state by far the most infamous one. They can be also be blamed for doing two semantically-unrelated things at the same time: asides from playing their main role, whatever this might be, they’re responsible for controlling how the originating classes should be instantiated as well. Singletons are entities unavoidably cursed with the obligation of performing at least two different tasks which aren’t even remotely related to each other. It’s easy to stay away from Singletons without feeling a pinch of guilt. But how can one be pragmatic in more generic, day-to-day situations, and design classes whose concerns are decently cohesive? Even when there’s no straight answer to the question, it’s possible to adhere in general to the rules of the Single Responsibility Principle, whose formal definition states the following:

There should never be more than one reason for a class to change.
What the principle attempts to promote is that classes must always be designed to expose only one area of concern and the set of operations they define and implement must be aimed at fulfilling that concern in particular and nothing else. In other words, a class should only change in response to the execution of those semantically-related operations. If it ever needs to change in response to another, totally unrelated operation, then it’s clear the class has more than one responsibility. Let’s move along and code a few digestible examples so that we can see how to take advantage of the principle’s benefits.

A Typical Violation of the Single Responsibility Principle

For obvious reasons, there’s plenty of situations where a seemingly-cohesive set of operations assigned to a class actually scope different unrelated responsibilities, hence violating the principle. One that I find particularly instructive is the Active Record Pattern because of the momentum it has gained in the last few years. Let’s pretend we’re a blind worshiper of the pattern and of the so-called database model and want to appeal to its niceties for creating a basic entity class which should model the data and behavior of generic users. Such a class, along with the contract that it implements, could be defined as follows:
<?php
namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
    public function getGravatar();

    public function findById($id);
    public function insert();
    public function update();
    public function delete();
}
<?php
namespace Model;
use LibraryDatabaseDatabaseAdapterInterface;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;
    private $db;
    private $table = "users";

    public function __construct(DatabaseAdapterInterface $db) {
        $this->db = $db;
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The user ID has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
                "The user ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setName($name) {
        if (strlen($name) < 2 || strlen($name) > 30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }
    
    public function getName() {
        if ($this->name === null) {
            throw new UnexpectedValueException(
                "The user name has not been set.");
        }
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }
    
    public function getEmail() {
        if ($this->email === null) {
            throw new UnexpectedValueException(
                "The user email has not been set.");
        }
        return $this->email;
    }
    
    public function getGravatar($size = 70, $default = "monsterid") {
        return "http://www.gravatar.com/avatar/" .
            md5(strtolower($this->getEmail())) .
            "?s=" . (integer) $size .
            "&d=" . urlencode($default) .
            "&r=G";
    }
    
    public function findById($id) {
        $this->db->select($this->table,
            ["id" => $id]);
        if (!$row = $this->db->fetch()) {
            return null;
        }
        $user = new User($this->db);
        $user->setId($row["id"])
             ->setName($row["name"])
             ->setEmail($row["email"]);
        return $user;
    }
    
    public function insert() {
        $this->db->insert($this->table, [
            "name"  => $this->getName(), 
            "email" => $this->getEmail()
        ]);
    }
    
    public function update() {
        $this->db->update($this->table, [
                "name"  => $this->getName(), 
                "email" => $this->getEmail()], 
            "id = {$this->id}");
    }

    public function delete() {
        $this->db->delete($this->table,
            "id = {$this->id}");
    }
}
As one might expect from a typical implementation of Active Record, the User class is pretty much a messy structure which mingles chunks of business logic such as setting/getting usernames and email addresses, and even generating some nifty Gravatars on the fly, with data access. Is this a violation of the Single Responsibility Principle? Well, unquestionably it is, as the class exposes to the outside world two different responsibilities which by no means have a true semantic relationship with each other. Even without going through the class’ implementation and just scanning its interface, it’s clear to see that the CRUD methods should be placed in the data access layer completely insulated from where the mutators/accessors live and breath. In this case, the result of executing the findById()
method for instance will change the state of the class while any call to the setters impact the CRUD operations as well. This implies there are two overlapped responsibilities coexisting here which makes the class change in response to different requirements. Of course, if you’re anything like me you’ll be wondering how to turn User into an Single Responsibility Principle-compliant structure without too much hassle during the refactoring process. The first modification that should be introduced is to keep all the domain logic within the class’ boundaries while moving away the one that deals with data access… yes, to the data access layer. There are a few nifty ways to accomplish this, but considering that the responsibilities should be sprinkled across multiple layers, the use of a data mapper is an efficient approach that permits to do this in a fairly painless fashion.

Putting Data Access Logic in a Data Mapper

The best way to keep the class’ responsibilities (the domain-related ones, of course) isolated from the ones that deal with data access is via a basic data mapper. The one below does a decent job when it comes to dividing up the responsibilities in question:
<?php
namespace Mapper;
use ModelUserInterface;

interface UserMapperInterface
{
    public function findById($id);
    public function insert(UserInterface $user);
    public function update(UserInterface $user);
    public function delete($id);
}
<?php
namespace Mapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUserInterface,
    ModelUser;

class UserMapper implements UserMapperInterface
{
    private $db;
    private $table = "users";
    
    public function __construct(DatabaseAdapterInterface $db) {
        $this->db = $db;
    }
    
    public function findById($id) {
        $this->db->select($this->table, ["id" => $id]);
        if (!$row = $this->db->fetch()) {
            return null;
        }
        return $this->loadUser($row);
    }
    
    public function insert(UserInterface $user) {
        return $this->db->insert($this->table, [
            "name"  => $user->getName(), 
            "email" => $user->getEmail()
        ]);
    }
    
    public function update(UserInterface $user) {
        return $this->db->update($this->table, [
            "name"  => $user->getName(), 
            "email" => $user->getEmail()
        ], 
        "id = {$user->getId()}");
    }
    
    public function delete($id) {
        if ($id instanceof UserInterface) {
            $id = $id->getId();
        }
        return $this->db->delete($this->table, "id = $id");
    }
    
    private function loadUser(array $row) {
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user;
    }
}
Looking at the mapper’s contract, it’s easy to see how nice the CRUD operations that polluted the User class’ ecosystem before have been placed inside a cohesive set which is now part of the raw infrastructure instead of the domain layer. This single modification should let us refactor the domain class and turn it into a cleaner, more distilled structure that conforms to the principle:
<?php
namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
    public function getGravatar();
}
<?php
namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }
    
    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The user ID has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
                "The user ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setName($name) {
        if (strlen($name) < 2 || strlen($name) > 30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }
    
    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }
    
    public function getEmail() {
        return $this->email;
    }
     
    public function getGravatar($size = 70, $default = "monsterid") {
        return "http://www.gravatar.com/avatar/" .
            md5(strtolower($this->email)) .
            "?s=" . (integer) $size .
            "&d=" . urlencode($default) .
            "&r=G";
    }
}
It could be said that User
now has a better designed implementation as the batch of operations it performs not only are pure domain logic, but they’re semantically bound to each other. In Single Responsibility Principle parlance, the class has only one well-defined responsibility which is exclusively and intimately related to handling user data. No more, no less. Of course, the example would look half-backed if I don’t show you how to get the mapper doing all the data access stuff while keeping the User class persistence agnostic:
<?php
$db = new PdoAdapter("mysql:dbname=test", "myusername",
    "mypassword");
$userMapper = new UserMapper($db);

// Display user data
$user = $userMapper->findById(1);
echo $user->getName() . ' ' . $user->getEmail() .
    '<img src="' . $user->getGravatar() . '">';

// Insert a new user
$user = new User("John Doe", "john@example.com");
$userMapper->insert($user);

// Update a user
$user = $userMapper->findById(2);
$user->setName("Jack");
$userMapper->update($user);

// Delete a user    
$userMapper->delete(3);
While the example is unquestionably trivial, it does show pretty clearly how the fact of having delegated the responsibility for executing the CRUD operations to the data mapper permits us to deal with user objects whose sole area of concern is to handle exclusively domain logic. At this point, the objects’ tasks are principle-compliant, nicely distilled, and narrowed to setting/retrieving user data and rendering the associated gravatars instead of being focused additionally on persisting that data in the storage.

Closing Remarks

Perhaps just a biased opinion based on my own experience as developer (so take it at face value), I’d dare to say the Single Responsibility Principle’s worst curse and certainly the reason why it’s so blatantly ignored in practice is the pragmatism of reality. Obviously it’s a lot easier “to get the job done” and struggle with tight deadlines by blindly assigning a bunch of roles to a class, without thinking if they’re semantically related to each other. Even in enterprise environments, where the use of contracts for outlining explicitly the behavior of application components isn’t just a luxury but a must, it’s pretty difficult to figure out how to group together cohesively a set of operations. Even though, it’s doesn’t hurt to take some time and design cleaner classes that don’t mix up unnecessarily heaps of unrelated responsibilities. In that sense, the principle is just a guideline that will assist you in the process, but certainly a very valuable one. Image via Fotolia

Frequently Asked Questions about the Single Responsibility Principle

What is the Single Responsibility Principle (SRP) in PHP Laravel?

The Single Responsibility Principle (SRP) in PHP Laravel is a design principle that suggests that a class should have only one reason to change. This means that a class should only have one job or responsibility. This principle is part of the SOLID principles, a set of five principles used in object-oriented design to make software designs more understandable, flexible, and maintainable.

How can I apply the Single Responsibility Principle in PHP Laravel?

Applying the SRP in PHP Laravel involves breaking down your classes into smaller, more manageable parts, each with a single responsibility. For instance, if you have a class that handles user registration and login, you could split this into two separate classes – one for registration and one for login. This way, each class has a single responsibility, making your code easier to maintain and understand.

What are the benefits of using the Single Responsibility Principle?

The SRP offers several benefits. Firstly, it makes your code more readable and maintainable, as each class or module has a single responsibility. Secondly, it reduces the risk of bugs, as changes to one class or module won’t affect others. Lastly, it makes it easier to test your code, as each class or module can be tested independently.

Can you provide a real-world example of the Single Responsibility Principle in PHP?

Sure, let’s consider a simple example. Suppose you have a class ‘Order’ that handles both the creation of an order and the printing of the order details. According to the SRP, these two responsibilities should be separated into two classes. So, you could create a ‘CreateOrder’ class for creating an order and a ‘PrintOrder’ class for printing the order details.

How does the Single Responsibility Principle relate to other SOLID principles?

The SRP is the first principle in the SOLID acronym. It lays the foundation for the other principles, which are: Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. All these principles aim to make your code more understandable, flexible, and maintainable.

What happens if I don’t follow the Single Responsibility Principle?

Not following the SRP can lead to code that is difficult to maintain and understand. Changes to one part of your code could have unintended consequences on other parts, leading to bugs. It can also make your code harder to test, as each class or module may have multiple responsibilities.

Is the Single Responsibility Principle applicable only to PHP?

No, the SRP is a general principle of object-oriented design and can be applied to any object-oriented programming language, not just PHP. It’s a principle that helps ensure your code is well-structured and easy to maintain, regardless of the specific language you’re using.

How does the Single Responsibility Principle improve code testing?

By ensuring that each class or module has a single responsibility, the SRP makes your code easier to test. You can test each class or module independently, which simplifies the testing process and makes it easier to identify and fix bugs.

Can the Single Responsibility Principle be applied to functions or methods?

Yes, the SRP can also be applied to functions or methods. Just like with classes, each function or method should have a single responsibility. This makes your functions or methods easier to understand, test, and maintain.

Are there any drawbacks to using the Single Responsibility Principle?

While the SRP has many benefits, it’s not without its drawbacks. One potential drawback is that it can lead to a larger number of smaller classes or modules, which can be harder to manage. However, with good organization and naming conventions, this can be mitigated.

Alejandro GervasioAlejandro Gervasio
View Author

Alejandro Gervasio is a senior System Analyst from Argentina who has been involved in software development since the mid-80's. He has more than 12 years of experience in PHP development, 10 years in Java Programming, Object-Oriented Design, and most of the client-side technologies available out there.

Advanced
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week