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:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Http\Request;
6 
7class PostController extends Controller
8{
9 //
10}

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.

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

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

1Route::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.

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Post;
6 
7class PostController
8{
9 public function __invoke(Post $post)
10 {
11 return view('posts.show', compact('post'));
12 }
13}

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
I'm a real developer ™
Michael Dyrynda

@michaeldyrynda

I am a software developer specialising in PHP and the Laravel Framework, and a freelancer, blogger, and podcaster by night.

Proudly hosted with Vultr

Syntax highlighting by Torchlight