Simplifying controllers
In this blog post, I'd like to highlight two tips to make controllers in Laravel feel much lighter.
Controllers don't need to extend anything
At the time of this writing, this is what you get when you run php artisan make:controller
:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MyController extends Controller
{
//
}
This controller extends a base controller App\Http\Controllers\Controller
. This is the content of that base controller.
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
That controller extends another one: Illuminate\Routing\Controller
. To keep this post short, I'm not going to paste the contents of that one here. Just know that it contains some functions to register middleware and some functions to call other methods on a controller.
There seems to be a widespread belief that, in order for a controller to work, it should extend the Illuminate
-controller. But you know what? Controllers don't need to extend anything. Here's an example of a perfectly valid controller.
namespace App\Http\Controllers;
class MyController
{
// just add some functions
}
In all our client projects at Spatie the controllers don't extend anything by default. If a controller needs to validate or authorize a request, we use the AuthorizesRequests
or ValidatesRequests
trait on that controller.
Controllers are better off without a default namespace
In a vanilla Laravel app, a controller lives in the App\Http\Controllers
namespace. This is being set up by default in the RouteServiceProvider
by that namespace
call.
namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
// ..
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
}
This enables us to just use a string in the routes file to for example route an URL to a controller like App\Http\Controllers\MyController
:
// in the web routes file
Route::get('my-url', 'MyController@index');
Sure, that default namespace saves us some keystrokes in the routes file, but that's the only advantage it gives us.
Let's see what advantages we gain by not setting up a default namespace. If you delete that protected $namespace
and ->namespace
call in the RouteServiceProvider
you'll have to specify the fully qualified namespace in the routes file.
// in the web routes file
use App\Http\Controllers\MyController;
Route::get('my-url', [MyController::class, 'index']);
If you only use one method in your controller, you could opt to use __invoke()
. By using that method, you don't need that tuple notation anymore in the routes file.
// in the web routes file
use App\Http\Controllers\MyController;
// isn't this beautiful?
Route::get('my-url', MyController::class);
Replacing a short string based reference by a the full qualified namespace of a controller has several benefits. If you use an IDE like PhpStorm, it now knows which class is being used. When you start typing a class, it'll try to autocomplete it. You can also click through the MyController::class
. Nice! If you refactor your controller or its namespace, PhpStorm will automatically update the routes file. Those are all big wins!
Also, if you now want to use another namespace for your controller, you can simply do so:
// in the web routes file
// alternative namespace
use App\Front\Http\AlternativeNamespace\MyController;
Route::get('my-url', MyController::class);
In closing
Laravel tries to make it easy for newcomers by providing some traits to scaffolded controllers by default and by setting up a default namespace. But if you have a bit of experience, I highly recommend not letting your controller extend anything by default and not using a default namespace.
Maybe the next version of Laravel should scaffold a class (without extending anything) as a controller and not use a default namespace. Do you agree? Let me know why or why not in the comments below.
What are your thoughts on "Simplifying controllers"?