Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

silence's avatar

I had the chance to try again Laravel 5 and here are my two cents about route annotations:

Route annotations can save you a lot of time in projects or the part of the projects where the URIs are not crucial, for example: Admin panels, APIs, etc. You need to create a method in a controller and you can add the route really quickly above the method definition using less characters, so it saves some time and doesn't distract you of your task (implement a new action).

But in some projects where you are worried about SEO and the routes order really matters and in complex projects where you are trying to implement several languages and domains / subdomains, I find the old way (routes.php) to be better, because you can implement logic, variables, etc.

So I could even combine both approach in a single project: route annotations for the admin panel, and routes.php for the public interface.

Taylor removed the routes.php file but it's really easy to add it again or distribute your routes in another way (for example a routes folder).

Also @JeffreyWay pointed it out that they won't remove the routes.php option, so I guess it's ok, everyone can decide wether it's more convenient using annotations or not.

P.S.: I knew subscribing myself to this topic was a really bad idea.

puzbie's avatar

But look on the bright side. People as a rule, hate change. Laravel 5, by its nature, is changing stuff. The debates are heated, but they are in the main, civil.

xingfucoder's avatar

In a few weeks, when we are working with a stable Laravel 5 version directly, we will not be using routes.php and maybe we will be making a pull request for removing that file.

Keep calm and make annotations :)

alfrednutile's avatar

Just an update This is the way now to add the "filters"

/**
 * @Middleware("auth")
 * @Middleware("csrf", on={"post", "put", "delete"})
 */
class ProjectsController extends BaseController {

no longer

/**
 * @Before("auth")
 * @Before("csrf", on={"post", "put", "delete"})
 */
class ProjectsController extends BaseController {
2 likes
JeffreyWay's avatar

Or, without annotations:

$router->get('foo', ['middleware' => 'auth', 'uses' => 'FooController@bar']);
3 likes
isimmons's avatar

Doesn't seem to be a way to pass multiple middleware with a single annotation. That could be an improvement in cases where there are many middlewares applied to one method. Anyone know if there is a syntax for doing that?

Would be nice to change this

/**
  * This method does foo
  * @Post("foo", as="foo.bar")
  * @middleware("auth")
  * @middleware("csrf")
  * @middleware("foo")
  * @middleware("bar")
  * @middleware("baz")
  *
  * @return Response
  */

to this

/**
  * This method does foo
  * @Post("foo", as="foo.bar")
  * @middleware("auth","csrf","foo","bar","baz")
  * 
  * @return Response
  */

or this

/**
  * This method does foo
  * @Post("foo", as="foo.bar")
  * @middleware("auth|csrf|foo|bar|baz")
  * 
  * @return Response
  */

or this

/**
  * This method does foo
  * @Post("foo", as="foo.bar")
  * @middleware(["auth","csrf","foo","bar","baz"])
  * 
  * @return Response
  */

I suppose using class level annotations with only and except would help cut down on things because you only have to type all those annotations once at the top of the class.

scarlac's avatar

+1 for keeping the routes.php file.

Route annotations should be entirely optional. I get why you'd want to use it - it can be very convenient if you are the sole developer or using a lot of Route::resource(...) or Route::controller(...). I keep seeing recommendations that you should avoid the two because keeping routes.php is good for readability and maintenance. While there's a place for both ::resource and ::controller, I find annotations for routes a hack to accomplish something that's already simple and effective. If it actually generates a routes file then shouldn't that obviously be the file you should work with? Don't make the default more complex if it can be simple. If you need more structure for a gigantic project you are still free to write/change the routes file manually as you were in 4.2.

I'm guessing the routes.php was removed to keep the directory structure clean. I understand the need for simplicity in the structure - but the alternative is so much worse. Please keep the routes.php file.

robwinkky's avatar

I like the annotations, but when I was practicing, I didn't use the "php artisan clear-compiled" first. It was driving me bonkers and I was about to rage about sticking to the routes.php as the default. Now there is no turning back, just make sure to "php artisan clear-compiled" first!

2 likes
demians's avatar

@JeffreyWay I never meant to say people who use annotations are idiots. I'd question the design pattern they've selected for their project, and depending on how strongly they've coupled to them, I'd choose not to use their code in my apps. But I can't say they are idiots because of bad design decisions. We've all made (and make) our share of those.

I understand that routes.php won't be deprecated. I think it should, someday, given we find a better way to declare our routes than an old-fashioned require_once call hard-coded somewhere in a vendor package.

But I really hope Annotations is not considered "the better way to declare our routes". Because I clearly don't agree with that design decision, and I feel (as others suggested) that innovation needs room, and usually new features deprecate old features in the long run. Either that, or Laravel v9 will have so many options, that its (currently beautifully smooth) learning curve will be a brick wall.

And, just to clarify something here, this usage of annotations as metadata for code generation is... pretty much OK. Your code doesn't depend on them, and you could erase them without breaking stuff (at least before rerunning the artisan route:scan command.) I'm much more concerned with runtime dependency through annotations.

And, as I said before, where I work, it is my responsibility to promote good practices over anti-patterns and code smells. And PHP's annotations have a huge potential of breaking stuff.

Anyway, I may have written some rants before in this topic, but this one is a bit more serious and centered ;-)

Sito's avatar

Hello, I am someone who was just learning L4, when L5 came out. Now it took me just a couple of hours to really understand the Routing annotations in L5. In the first couple minutes, I became totally depressed when finding out the system I just learned, was completely changed. But now, two days later, I fully understand it and think it is fantastic, it makes work so much easier! my compliments :-)

I think when ya used to 'the old'....maby then ya could think sh*t! but for me as 'newbie' it was extremely simple, so I hope this will help eases someone's mind ;-) my conclusion: I think this is way better then it was before, at least easier, if ya know what you're doing.

1 like
alfrednutile's avatar

@tag right now I still use the method of the controller that will handle the request.

/**
 *@Get('foo')
*/
public function foo($bar)
{
    //do something
}

Does that help?

MThomas's avatar

@tag isn't that just this (or did I misunderstood you).

/**
 * @Middleware("yourfilter")
 * @Get("foo")
*/
public function foo($bar)
{
    //do something
}

Or for the whole class

/**
 * @Middleware("yourfilter")
*/ 
class Bar {

    /**
     *@Get('foo')
    */
    public function foo($bar)
    {
        //do something
    }
}
ATOM-Group's avatar

Yes, I think you misunderstood :P

Here's the example from the Laravel documentation for 4.2:

Route::filter('age', function($route, $request, $value)
{
    //
});

Route::get('user', array('before' => 'age:200', function()
{
    return 'Hello World';
}));

Note that I can create a re-usable filter but pass in a parameter to it (200, represented by the $value argument of the filter itself). In the real world, it would be something like this:

(and yes, I'm using the routes.php file, I don't like annotations)

 Route::get('/admin/forums/create', ['middleware' => 'permission:forum.create', ..... ]);

I could give the route a name, and simply reference that in the $request->route() of the middleware, which is my fallback solution, but I would prefer to be able to pass in a parameter to the middleware directly.

ATOM-Group's avatar

Hmmm, well I hope that Taylor either addresses it so that middleware can behave like filters, or bring back the FilterServiceProvider rather than trying to sideline the more flexible filters in favor of middleware.

I know 5 is still under heavy development, I just hope that small but important details like this don't get lost in the shuffle.

ATOM-Group's avatar

Wait, so THIS is why my fresh installation of the latest L5 was completely ignoring my RouteServiceProvider changes entirely?

I'm going to assume that there will be a decision down the line to make LOCAL development the default environment for the framework, and that any sort of compiling/caching that requires users to run commands will be turned off for local?

That way you can start using Laravel right out the box without doing all of this boiler plate stuff.

98% the time you're working on Laravel, you're working on it locally. 2% of the time you're deploying to staging or production, thus it makes more sense to offload the boilerplate stuff to the infrequent steps (like deployment) so as to keep local development in a streamlined, "it just works right out of the box" state.

garrettroach's avatar

In regards to route notations. How do you set an entry level controller that is called by default?

garrettroach's avatar

In regards to route notations. How do you set an entry level controller that is called by default?

garrettroach's avatar

If I use route notations exclusively, how would I define my root entry controller?

bestmomo's avatar

Not sure to understand. It's quite like route file. Could you give an example with route file that must be converted with annotations ?

garrettroach's avatar

Never mind, I wasn't thinking straight. Another question. I want to route prefix all of my controllers to the same route. How would I do this without having to repeat myself in every controller?

bestmomo's avatar

For now I think your cant group annotations for controllers.

fulup's avatar

Annotation and object inheritance ?

I have multiple controllers for social login that inherit from a shared parent class. This parent class implement common methods like: user-consent, email-verification, etc ...

I try to generate URL specific to each social provider. This for every method including the one in my parent class. Example:

  • base-class specific method: | orange/login | google/login | facebook/login
  • parent-class common code | orange/consent | google/consent | facebook/consent

If I place an annotation per method in the parent-class, route:scan fail because I got the same route name for multiple controllers. If I try to place route annotation at the top of specific provider class, nothing happen [either my syntax is wrong, or its not possible]

Question: what would be the syntax to implement at base-class level a route for a method that is implemented at parent-class level? If this is not possible what would be the solution to solve such an equation.

I tried with not success the following annotations just before my OrangeController Class definition. Route:Scan does not complain, but nothing happen :(

- @Get("/user/orange/consent" , uses="consentGet")
- @Post("/user/orange/consent", uses="consentPost")

I have the same issue for MiddleWare as a global definition look like not impacting the parent methods as well.

By the way, I was not able to find a reference documentation on L5 annotation, does such a document exist ?

MartelliEnrico's avatar

@fulup: Since is still PHP, I think you could use PHP logic inside the annotation. I haven't tried this, but it could work:

abstract class Parent {
    abstract static function getRouteName();
    /* ... */
}

and inside the child class implement the method and return a string with the url/name, and edit the annotation like this:

@Get("/user/{static::getRouteName()}/consent" , uses="consentGet")
@Post("/user/{static::getRouteName()}/consent", uses="consentPost")

This should automatically use the child value when reading the annotation. Try it and tell us if it works.

fulup's avatar

Using PHP parent call would be more work to maintain, than rolling back to L4 route.php model :)

Digging L5 annotation logic source code. I find out a working solution for routes. Prefix all methods route with @Controller(prefix=xxx) before class definition. Then provide only relative routes at method level.

All methods at both local & parent classes level inherit from controller prefix route. It imposes for me to change my 'url' pattern but it works. And it's very easy to maintain.

  • At controller level before 'class': @Controller(prefix="/user/orange")
  • At method level [for both local and parent class]: @Get("login")

The only remaining non working annotation is middleware exception at controller level

From http://mattstauffer.co/blog/laravel-5.0-route-annotations page I would expect except syntax to work, but still need to work this out. Should the expect be method name, route path ??

fulup's avatar

Find the solution for @middleware. Mattstauffer's blog syntax works. You should use method name and not route path our routename in the exclusion list.

/**
 * @Middleware("idpSessionKey", except={"socialLogin"})
 * @Controller(prefix="/user/facebook")
 */
class FacebookController extends _SocialController {

    public function __construct(SocialiteFactory $socialite, Guard $auth) {
        parent::__construct ('facebook', $socialite, $auth);
    }

    private function getAuthorizationFirst()  {
        return $this->socialite->driver('facebook')->scopes(['email'])->redirect();
    }

    private function getIdpUser()   {
        return $this->socialite->driver('facebook')->user();
    }

    /**
     * @Get("login")
     *
     * Method executes OAuth2 login and either returns a logged user
     * or redirects to consent pages if user is logged at social level
     * but unknown in our local user base.
     */
    public function socialLogin (HttpRequest $request) {
    ... etc ...

For parent class just use relative routes, they will be prefixed and protected with @controller and @midleware values at controller level.

class _SocialController extends _SuperController implements LoginListener {
    protected $auth;
    /**
     * @Get("consent")
     * We are redirected to this consent page by SocialProviders providers
     */
    public function consentGet () {
        $idpuser = Session::get ("idpuser");
        Session::flash ("_old_input", ['username' => $idpuser->name, 'email' =>     $idpuser->email, 'idp-login-img' => $this->idpimg]);
        return view ('auth.userconsent');
    }

This model allows to have a common class for all sociallogin provider, but nevertheless different routes to point on the right object instance, during consent, email verification, etc ...

This being said, it might not be the best option. Any one with a better solution, please let me know.

Please or to participate in this conversation.