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 anUnexpectedValueException
because route compilation happens as part of the bootstrap process.