How Eloquent breaks the Single Responsibility Principle, and why I’m okay with it

Hi everyone

I thought I would discuss a controversial one today, in order to give an articulated response to a common argument about Eloquent (and generally the Active Record pattern)

Firstly, just in case you don’t know, here are some definitions:

Single Responsibility PrincipleThe “S” in the “SOLID” principles. In short, this means one class should have one job. The idea is to keep your code modular and easy to switch out.

SOLID Principles: I’ll do an article on this one day, but it’s a discussion for another time. Like all principles or methodologies they’re like the Pirate’s Code, they’re more like guidelines than hard and fast rules of software design.

Eloquent: Is an ORM (object relational model) which comes as standard with Laravel, and is absolutely fantastic

Active Record: Is a design pattern whereby a record from a database table is returned as an object that can be manipulated and can update the database as part of its role. Usually this means you can do something like

$myRecord = RecordFactoryOrSomething::getTheRecord();
$myRecord->field = 'Updated Value';
$myRecord->save();

This is quite handy for programming as you can store all of the logic to do with saving in the Model.

Laravel: Is a framework for Rapid Application Development, which, like Eloquent, is amazing. It basically gives you all of the tools you need to build bespoke software, but in a way that means you can do it at lightening speed, with crazy accuracy. I can’t sell Laravel well enough, honestly it’s amazing.

So, now that the introductions are out of the way; the Single Responsibility Principle isn’t mandatory

I’d like to make my first point, which quite simply is: The Single Responsibility Principle is a principle. I joked earlier about principles being like the Pirate’s Code, but one does have to be careful when using design patterns and principles, if we’re not careful they become anti-patterns which essentially means their over usage and religious subscription actually causes more problems in the software than it would’ve done had it have been omitted completely.

All patterns should be used in a way which makes sense and makes the code more efficient and maintainable. Ultimately maintainable code which is easy to extend is cheaper for the business whose employing you. Or means your profit margin is higher if you’re running your own business, freelancing or contracting.

Single Responsibility is pretty subjective

This is another slight issue I have with the Single Responsibility Principle. I actually love the principle itself. It makes sense, separate things into logical blocks, and if each class has its own responsibility (and only the one) then it’s quite easy to find how the classes are supposed to work together, perfick.

However, a single responsibility has to be defined. If you defined the single responsibility of a class as “do the business logic” then you could have a God class, which technically has a single responsibility. That would, obviously be a bad usage.

So, let’s look at the other end of the spectrum and go into tiny, tiny detail. Do I need a class for executing SQL queries, and one for generating them? Maybe that means I need a class which prepares INSERT statements, another for UPDATE, and so on and so forth.

The latter approach to single responsibility is most definitely an anti-pattern. That level of abstraction and obscurification will only ever be bad for managing and debugging code.

So then how does the Eloquent Model break it?

The big problem people have with Eloquent is that the Model class fulfils multiple responsibilities. You can’t really say “it is responsible for interacting with a specific table” because it is also representational of a record.

That means it has, at least, two responsibilities: Executing queries against a table, and representing a record. The power of the Eloquent Model is that it actually does more than that, it also defines relationships etc.

So, let’s be honest, it rips the Single Responsibility Principle apart, but I’m totally okay with that.

But, if you love SRP, how can you like the Eloquent Model, you heathen?

The reason I love it, is because what you actually do is group those few responsibilities in an entirely sensical and logical place, you end up with a class which can give you lots of information; all of those responsibilities can be so beautifully wrapped; as so you end up with something like this:

NB: I’ve omitted a lot of code here, just to demonstrate my point I didn’t need it

class User extends Model
{

    // This is a query run against the table
    public static function findUserOnEmail($emailAddress){}

    // This is an action (probably running a query)
    public function changeUserLevel($userLevelId){}

    // This is a relationship defined
    public function friends()
    {
        return $this->hasMany('\ModelNamespace\Friendship');
    }

}

And there you have it, in a few lines of code we have some functionality we need on the table to complete search functionality which has to otherwise be stored somewhere else, that usually doesn’t make sense.

You’ve got an action that can be publicly exposed to other developers to carry out a task without any prior knowledge of the data architecture of your system or how the user levels.

And you’ve got an easy way to get all of the friends for that user.

This is why I love Eloquent. Because it puts aside rules and methodologies for a logical and sensical reason, in order to provide an exceptionally transparent way to manage your data layer.

But wait, there’s more!

All of this is without things like the hookable functions so you can do certain functionality (think expire memcached objects, etc) based on creation, update, or save. There’s a huge amount of functionality built in, and it’s perfect for almost everything.

I’ll be honest, all ORMs fall down with certain complex queries – but it’s not supposed to tackle that problem. It’s the vast majority of monotonous tables and relationships which have to be constantly created to support any kind of enterprise software which becomes so much faster.

Alright, so what’s the alternative?

The alternative to this, realistically, is the Service-Mapper-Entity pattern. In short this means each table has an Entity responsible for representing the data. You then have a Mapper responsible for executing SQL, and then you have a Service which is the interface exposed to the rest of the application. So you tell your service to do something, and it does this using the Mapper, usually returning Entities.

Personally, that feels like too much code to do a simple task. And it is just a personal opinion and there are times that this is very useful, the thing that makes me really sway from that is that when I create 5 tables, with, let’s say 3 bridging tables to support many-to-many relationships; I suddenly can end up with 8 sets of Service, Mappers and Entities. This is 24 classes, which is a lot, compared to 8 classes; which define their relationships and are ready to be used with hydration and all of that stuff.

So, it’s okay to break the Single Responsibility Principle?

Before I ignite an absolute flame war, which this particular topic has the potential to do. In a word, yes. But only if you have a good reason, and you’ve done it to make your code maintainable, readable and easy to manage; without sacrificing security or anything like that.

In this particular instance, I think breaking the principle is fine. It makes sense, and it makes for, even on large scale applications with a large data layer, an easy way of managing this data layer and managing it in a way which is sustainable moving forward.

And how do I keep it clean?

For me personally, follow two major rules.

  1. Static methods do something to the table itself – this could be searching, filtering, retrieving, etc.
  2. Methods on the class, do something to the record – I often like to wrap common functionality in a method, it makes it easily accessible to everyone else

For example; let’s say we activate a user account, there are two ways of approaching this:

// Fetch the record with ID of 17
$record = User::find(17);

// Set the is_user_active field to 1
$record->is_user_active = 1;

// Save this record
$record->save();

There isn’t necessarily anything wrong with this. Until I want to hook into that functionality, or I change something else, let’s say I nullify the activation_key field, for example. Then I would have to go and find everywhere I’ve done this. I don’t like this, it’s a bit awkward.

So I prefer to do something like this

// Fetch the record with ID of 17
$record = User::find(17);

// Activate the record
$record->activate();

// Within your User model class
public function activate()
{
    $this->is_user_active = 1;
    $this->save();
}

Disclaimer: You can do this using the updated method on in Eloquent, so you could hook into this and check to see if the is_user_active field has changed and then hook in that way; word of caution with that though – you can end up with a hell of a lot of if statements and it can get quite messy.

In summary, Eloquent is awesome

  1. It let’s you have an easy to manage data layer for your application
  2. The Model class does break the Single Responsibility Principle, but it can be argued that Model class is responsible for representing and interacting with a single table
  3. You end up with easy to plug into user-land code
  4. You can manage your relationships beautifully

So really, when weighing that up I can’t see any reason not to use it. It’s extendible (I’ve extended the base Eloquent Model multiple times, with a lot of success), you can overwrite functionality where you want to really easy – though you don’t often need to, because the functions you can plug into are so useful.

As always I’d be really interested to hear any thoughts you guys have on this, especially as I know it’s a bit of a controversial one!

One Comment

  1. Pingback:Quick and Easy PHP Singleton - JohnoTheCoder

I'd love to hear your opinion on what I've written. Anecdotes are always welcome.

This site uses Akismet to reduce spam. Learn how your comment data is processed.