Jens Segers on

Goodbye controllers, hello request handlers

A lot has changed in the PHP landscape over the past years. We started using more design patterns and things like the DRY and SOLID principles. But not a lot has changed for Controllers right?

If you have worked on large applications before, you might have noticed that you end up with bloated controllers sooner or later. Even if you use repositories or service classes to extract logic from the controller, the amount of dependencies, methods and lines of code will grow over time.

Let me introduce you to request handlers. The concept is very simple, yet very unknown to a lot of PHP developers. A request handler is basically a single action controller, which results in a more clear request to response flow. This concept has similarities with the Action-Domain-Responder pattern which was proposed by Paul M. Jones, an alternative for the MVC pattern.

A great way to build request handlers is using invokable classes. Invokable classes are classes that use PHP's magic __invoke method, turning them into a Callable, which allows them to be called as a function. Here is a simple example of an invokable class:

class Greeting
{
	public function __invoke($name)
	{
		echo 'Hello ' . $name;
	}
}

$welcome = new Greeting();
$welcome('John Doe'); // Hello John Doe

At this point you are probably thinking; "Why would I do that?". I know, this is a bit of an absurd example. But it makes a lot of sense when used in combination with code that expects callables and dependency injection. A great use case is request handling with routes in Laravel or Slim:

Route::get('/{name}', Greeting::class);

Did that just blow your mind? No? Let's compare that to what you would have normally written:

Route::get('/{name}', 'SomeController@greeting');

Still not? Other than nicer looking code, there are more benefits. Let me go ahead and compare some advantages of using request handlers vs. controllers.

Single responsibility

The very first principle of SOLID is the 'Single responsibility'. In my opinion, controllers with many actions kind of break this principle. Request handlers provide a nice solution to separate these actions into their own classes, making them easier to maintain, refactor and test.

Here's an example of 2 request handlers extracted from a UsersController, which handled both editing and saving of a user's profile:

class EditUserHandler
{
	public function __construct(
		UserRepository $repository,
		Twig $twig
	) {
		...
	}

	public function __invoke(Request $request, Response $response)
	{
		...
	}
}

class UpdateUserHandler
{
	public function __construct(
		UserRepository $repository,
		UpdateUserValidator $validator,
		ImageManager $resizer,
		Filesystem $storage
	) {
		...
	}

	public function __invoke(Request $request, Response $response)
	{
		...
	}
}

Which brings us to the next advantage;

Testability

Have you written unit tests for your controllers recently? You probably ended up creating mocked dependencies that were not even related to your test. Because request handlers split up the different controller actions into separate classes, you only have to inject or bind mocks for that specific action.

Recently Jeffrey Way suggested this on Twitter;

Testing Tip: Make your feature test classes as specific as possible, and then use each test to describe an important rule/ability.

This is basically what will happen naturally if you have a test file for each request handler. This is such a great improvement over those huge controller test files.

Refactoring

PhpStorm and other editors have powerful refactoring options. But if you use the default Laravel or Slim way of binding controller methods to routes, you probably ran into issues before. Renaming this:

Route::get('/{name}', Greeting::class);

Is just so much easier than this:

Route::get('/{name}', 'SomeController@greeting');

Conclusion

Request handlers offer a great alternative for controllers. Controller actions are split into individual request handler classes, responsible for a single action. This results in code that is easier to maintain, refactor and test.

Should you go ahead and replace all your controllers with request handlers? Probably not. For small applications it might make sense to group actions together just for simplicity. I only discovered request handlers when I started working at Teamleader, and I don't feel the need to go back to controllers anytime soon.

If something is unclear or you have questions, let me know by leaving a comment below and I will update this article.

Webmentions

Tweet about this blog post and you will appear below!