Michael Dyrynda
Home Blog Podcasts
 
Single action controllers in Laravel February 24th, 2017

Introduction

The Laravel documentation makes a passing mention of this functionality, which became available in Laravel 5.3 via PHP's __invoke magic method.

Basic structure

Given that we want a PostController, getting setup is very straight forward. Using php artisan make:controller PostController, will give you the following structure:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{
    //
}

You may consider managing your entities within an Admin controller namespace in a more traditional RESTful way. In this instance, I've found it useful to have my PostController responsible only for displaying a post on the public-facing part of my site.

Think of this PostController as though we've just taken the show method from a RESTful controller and placed it in its own class. We do this by adding the __invoke() method, leveraging Laravel's implicit route model binding, to do just that.

public function __invoke(Post $post)
{
    return view('posts.show', compact('post'));
}

Lastly, add a route which is responsible for this action in the routes/web.php file:

Route::get('/posts/{post}','PostController')->name('posts.show');

When routing to single action controllers, there is no need to specify a method as the action is assumed to be @__invoke when the method exists on the named class. If no action is specified and you forget the __invoke method, an UnexpectedValueException will be thrown by the router.

Refactoring

This controller is already quite elegant but can be simplified even further. For very simple controllers where none of Laravel's convenience features that the base controller offers are being used (middleware, validate, dispatch), you no longer need to extend from it.

<?php

namespace App\Http\Controllers;

use App\Post;

class PostController
{
    public function __invoke(Post $post)
    {
        return view('posts.show', compact('post'));
    }
}

I generally avoid suffixing classes with descriptors of their purpose, such as Interface or Trait, so I find it interesting that it is commonplace to suffix controllers with the word Controller.

In the instance of single action controllers, I'm comfortable with dropping Controller from the name, as a way of indicating to anybody scanning the directory structure that this class - implicitly a controller, given its location - is responsible for a single action.

You may consider naming the controller ShowPost as a way of being explicit about the controller's intent but I'd suggest caution with this approach; if you start seeing ShowPost, EditPost, CreatePost, etc controllers creeping into your codebase, I'd reconsider the RESTful approach. More controllers never hurt anybody, but we should be smart about when this is done!

Conclusion

Update 2017-04-18: If you're going to use single action controllers, make sure that you create the controller and add the __invoke method before you add the route definition, otherwise you'll end up with an UnexpectedValueException because route compilation happens as part of the bootstrap process.

UnexpectedValueException
I'm a real developer ™
Michael Dyrynda

@michaeldyrynda

I am a Lead Developer at NuSkope by day, and a freelancer, blogger, and podcaster by night.

Proudly hosted with Vultr