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

  • Single action controllers are an effective way to wrap up simple functionality into clearly named classes.
  • They can be used in instances where you're not necessarily following a RESTful approach; be careful not to separate multiple actions for a single entity across multiple controllers.
  • Where you might have previously used a single controller for multiple static pages, you could consider separate named controllers for each static pages.
  • You can add other methods to this class, but they should be related to the single action this controller is responsible for.

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