Unclutter Your Controllers: Using Policies

This article is part of the new series “How to unclutter your controllers”. This post shows you how to cleanup your code by using policy objects. I use the PHP with a Laravel flavour but you can easily adapt the patterns to another language  and framework that follows the glorious MVC  pattern. Don’t care to much about the used methods like response() if you’re not familiar with it.

The idea

Policies can be used to decouple your business rule implementation (rules for data validity, protection of actions) from the actual controller logic (Single Responsibility Principle).

How it works

Let’s whip up a small example scenario where you are a company that creates amazing and beautiful fluffy cats. A creation method could look like this:

Sweet, but now your boss decided to allow only 5 black cats to keep the world in balance? I’m pretty sure you will end up with something like this:

That code looks gross doesn’t it?

I mean okay, it still looks kind of okay, but what happens if your boss wants to reduce the number of cats in the world even more by some other silly rules?
Or if you just don’t want to allow the particular user to have cats? (I mean everything is possible right?). You see!

Abstraction to the rescue!

Let’s try to refactor that tiny little function that got a little bit out of wag:

uff okay, that looks quite complex with 2 new classes and interface in the mix. Scary… But let’s go through this together:

So we learned that a Object should have only one responsibility.
That means that our controller should do only one thing:

Controlling things: Telling the rest of the application “Hey, there is a (http) request, please do something with it!”.
So the first thing we do is to give the rules a new home by extracting it a dedicated policy class:

The controller get’s it’s brand new policy class injected:

That already looks way better but there is still some room for improvement: The Policy requires a http compatible companion to fullfill it’s duty. (the policy consumer must use a http type of error handling).

We have different possibilities to tackle this like throwing errors and propagating the error to the controller. But why don’t we try something else today! Nothing can stop us to just pass the controller instance to the policy right?

The beauty of this: We can now put the “HTTP” logic inside the controller where it belongs:

You could already stop there if you like. but If you want to improve the code even more you can consider several extra steps:

  • Give the Policy constructor a uniform home in a base class
  • Create an interface for the Controller
  • Using a trait (example in PHP)
  • Provide the policy an array of rules instead of using single function calls.

Give the Policy constructor a uniform home in a base class

Let’s create the base class:

And inherit from it like this:

You see, now we have a super tiny policy class.

Create interfaces

The interface tells the controller which methods and attributes need to be implemented. Now you can make always sure that the hasError method exists:

Using a trait (PHP only)

PHP also supports traits. These are nice and small includable code snippets for your class. It helps you to use Composition over Inheritance quite easy. Let’s use it for populating our policy logic to the target controller. Inheritance over a base class would be also a feasible way:

Now you can get rid of your hasError method inside the controller when you use the “normal way of error handling”. It also takes care of the contract between the policy and the controller so you don’t even need the interface anymore:

Apply method

When the rules are getting more and more consider a apply method that handles an array of rules and calls the corresponding methods inside your Policy class:

Conclusion

Patterns are amazing! It’s a bit of extra work at the beginning but if you use this technique in all of your controllers it simply adds up.

I hope that the above examples are helped you to increate the quality of your code.

See you in the next episode.
Elias