Jens Segers on Sep 22

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 why are we still using controllers?

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}', '[email protected]');

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}', '[email protected]');

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.


Comments

Alex Miles 5 days ago

Eduardo: Yes, although in Jens' example he uses class name resolution (Greeting::class), which makes it easier for the IDE to perform refactoring and code-completion out-of-the-box than if the class name was passed as a plain old string, as in the Laravel docs.


Tomas Votruba 1 week ago

I really like this approach moving from strings to

I've added similar support to Nette: https://www.tomasvotruba.cz/blog/2017/06/19/symbiotic-controller-nette-presenter-with-freedom/


Eduardo Aranda 1 week ago

Would it be the same as using single action controllers ?

https://laravel.com/docs/5.5/controllers


Piero Recchia 2 weeks ago

Great article, this approach is used in Zend Expressive, i used without know it name


Jens Segers 2 weeks ago

@shadowhand The request being passed as a parameter is something typical in Laravel, but not related to the request handler concept. Implementing this in PSR 15 framework would return a response like you described.


shadowhand 2 weeks ago

The only problem with this is that the handler should be returning a response, not receiving it as a parameter. In pseudo-code: fn(request): response is the ideal signature. If you are using PSR-7 then you probably want http-interop/http-factory to create those responses.

@Cherif using this pattern doesn't exclude the use of CQRS or CommandBus. The handler only deals with HTTP concerns and calls domain functions to produce the data needed for the response.


Max Gaurav 3 weeks ago

The article kind of describes the PORTO pattern over the standard MVC pattern.


Jens Segers 3 weeks ago

@nonya123 the Laravel code is just an example. It applies to any framework or router you use.


nonya123 3 weeks ago

a mostly good article, the negative part being the laravel typical reliance on static functions, rather than using a router service.. but nobody is perfect, I guess.


Mahmoud Zalt 3 weeks ago

Great article, you may need to check the Porto Architecture https://github.com/Mahmoudz/Porto


olvlvl 3 weeks ago

Great article, but I'd rather leave HTTP business to controllers and use a command dispatcher and command handlers, so I can use the same commands from CLI, messaging, or whatever… Nowadays, controllers are one in many input sources. You shouldn't have your business logic in there.


Cherif 3 weeks ago

I dont think this is a good implementation of ADR, it dosent remove the Controller/Action from the UI layer yes UI layer the Controller/Action and the view are the components of the UI. The handlers are Application Services simply so they accepts commands (your request) and the commands should be validated. What you did is a tight coupling between the UI and the Application layer (handlers) the UI is a client to the Application services and the application services should be clients to the domain and the handlers shouldn't depend on the templating system or any response you can use specialized infrastructure services for this like transformers


Jens Segers 3 weeks ago

@ericlagarda Depends on your framework/container. Laravel for example will auto wire dependencies on your constructor via reflection


ericlagarda 3 weeks ago

How can I inject a ModelRepository into a Handler? No way to do it. Thanks


Steve 3 weeks ago

I've been using this a lot quite recently, and I've found it refreshing. Yes, you end up with more classes, but each Action (Request Handler) is so descriptive and specific that it's easier to reason about.

@davedriesmans See pmjones/adr and Martin Bean's blog post

I've found that extending Laravel's default App\Http\Controllers\Controller worked pretty well, letting me hook up middleware, use one-off validation, etc. all those little controller conveniences.

If you start to feel like you're repeating yourself across a group of related Actions, just make a parent class or extract commonality to some traits.


Jens Segers 3 weeks ago

@Gunnlög ADR is the concept, request handlers are an implementation of the action.


Gunnlög 3 weeks ago

What is the difference to ADR? To me it looks like it is the same.


Jens Segers 3 weeks ago

@Mononeon that is indeed the simplified version. But anonymous functions don't have any of the advantages I described in the article.


Mononeon 3 weeks ago

$welcome = function () { echo 'Hello ' . $name; } $welcome('John Doe'); // Hello John Doe

wow Goodbye classes, hello functions


davedriesmans 4 weeks ago

What articles/books/videos would you suggest if you want to dig a bit deeper into it?


te7ahoudini 1 month ago

great article i really liked the idea


bdrasht 1 month ago

Thanks, would have been a nice talk on the last PHP meetup at Teamleader.