I'm currently organising Full Stack Europe. It's a conference in Antwerp, Belgium for developers who want to learn across the stack. You can use this link get your ticket with a nice discount.

Simplifying controllers

Original – by Freek Van der Herten – 4 minute read

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\Controllers\MyController\AlternativeNamespace;

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.

Stay up to date with all things Laravel, PHP, and JavaScript.

Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.