Using Route Annotations in Laravel 9

Tom Ellis
3 min readFeb 6, 2023

I’ve created a Laravel package that allows you to specify (or annotate) routes against your controller class using PHP 8 Attributes.

I was inspired by a feature I saw in the Symfony framework, called Route Annotations, which allows you to configure your routes in the controllers themselves, rather than in a separate routes file. So I decided to work on a port for it to work in Laravel.

It existed in the Symfony framework for years, but was configured using Docblocks. When PHP 8 was released and the Attributes feature came with it, the Routing component was updated so you could configure your routes in a controller class using PHP Attributes.

Now at the time of writing, the package is at version 0.1.1, as it needs a bit of testing, but it’s still useable and supports nearly all the available options.

Installation

Requirements

  • PHP 8.0.2+
  • Laravel 9.0+

To install this package please run:

composer require smashed-egg/laravel-route-annotation

Now that the package is installed, we’ll create a new controller

php artisan make:controller UsersControllers

Which will give us this:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UsersController extends Controller
{
//
}

Next we need to import the route annotation attribute class:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use SmashedEgg\LaravelRouteAnnotation\Route;

class UsersController extends Controller
{
//
}

And we’re good to go. We can now configure routes for each method in the controller.

We’ll do it bit by bit so you can see whats happening.

First we’ll add the attribute to the top level controller, this acts as a Route Group.

#[Route('/users', name: 'users.')]

Which is the equivalent of doing:

Route::group([
'as' => 'users.',
'prefix' => 'users',
'controller' => UsersController::class
], function() {

});

Which gives us:

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use SmashedEgg\LaravelRouteAnnotation\Route;

#[Route('/users', name: 'users.')]
class UsersController extends Controller
{
// Omitted for simplicity
}

Next we’re going to add a homepage to the controller along with its routing

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use SmashedEgg\LaravelRouteAnnotation\Route;

#[Route('/users', name: 'users.')]
class UsersController extends Controller
{
#[Route(uri: '/', name: 'index')]
public function index($type)
{

}
}

Which is the same as doing the following (including the Route group):

use App\Http\Controllers\UsersController;

Route::group([
'as' => 'users.',
'prefix' => 'users',
'controller' => UsersController::class
], function() {
Route::get('/', ['as' => 'index', 'uses' => 'index');
});

So far we’ve gone though the URI, name, prefix, controller and route groups.

You can configure your routes using the same options that are available doing it the standard way in your route files. We’ll go through each of them now.

Middleware

You can specify a list of middleware that should be applied to the whole controller (i.e route group) or a single action.

#[Route('/users', name: 'home', middleware: ['can:view-users'])]
public function index()
{
}

Route Parameter Constraints (Aka wheres)

You can specify route parameter constraints to make what is allowed in the URL strict. Below is an example of a regular expression constraint.

#[Route('/edit/{id}', name: 'edit', wheres: ['id' => '[0-9]+'])]
public function edit($id)
{
}

Route Parameter Defaults

You can specify default values for route parameter values that are optional.

#[Route('/users/filter/{filter?}', name: 'filter', defaults: ['filter' => 'all'])]
public function filter($filter)
{
}

Domain or Sub-domains

You can specify a domain or subdomain. Useful in route groups.

#[Route('/users', name: 'users.', domain: '{subdomain}.example.com')]
class UsersController extends Controller
{
// Body omitted
}

(Http) Methods

You can specify any HTTP Methods allow by the route group or single route.

#[Route('/users/create', name: 'create.post', methods: ['POST'])]

Pros

  1. Keeps your routing and controllers in the same place
  2. Takes advantage of newer PHP features

Cons

  1. Requires a slight learning curve and thought process, which I think is worth it.
  2. Differs to the standard fluent Laravel way you define routes.

Conclusion

Congratulations! You setup your routes using annotations, unless you only read this post….

Anyway, hope it’s something you’d like to test and play around with.

As I said needs a bit of testing still and tweaking before version 1.0 will be released, but I don’t think thats far away.

If you find any issues or want to contribute anything please head to the repo here.

--

--

Tom Ellis

PHP and JavaScript hacker. Symfony and Laravel tinkerer. Open source developer.