Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Routing Wildcards

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

This page has a boring, hardcoded URL. What our aquanauts deserve is a dynamic route that can handle the URL for any genus - like /genus/octopus or /genus/hippocampus which is the genus that sea horses belong to. Oh man, sea horses are cute.

How? Change the URL to /genus/{genusName}:

... lines 1 - 7
class GenusController
{
/**
* @Route("/genus/{genusName}")
*/
public function showAction($genusName)
... lines 14 - 16
}

This genusName part could be named anything: the important part is that it is surrounded by curly braces. As soon as you do this, you are allowed to have a $genusName argument to your controller. When we go to /genus/octopus this variable will be set to octopus. That's pretty awesome.

The important thing is that the routing wildcard matches the variable name.

To test this is, change the message in the response to 'The genus: '.$genusName:

... lines 1 - 7
class GenusController
{
/**
* @Route("/genus/{genusName}")
*/
public function showAction($genusName)
{
return new Response('The genus: '.$genusName);
}
}

Tip

Be careful when rendering direct user input (like we are here)! It introduces a security issue called XSS - read more about XSS here.

Head back to the browser and refresh. Ah! A 404 error. That's because the URL is no longer /genus, it's now /genus/something: we have to have something on the other side of the URL. Throw octopus on the end of that (/genus/octopus). There's the new message. And of course, we could change this to whatever we want.

So what would happen if the wildcard and the variable name didn't match? Um I don't know: let's try it: change the wildcard name and refresh!

... lines 1 - 7
class GenusController
{
/**
* @Route("/genus/{genusName2}")
*/
public function showAction($genusName)
... lines 14 - 16
}

OMG: that's a sweet error:

The Controller::showAction() requires that you provide a value for the $genusName argument.

What Symfony is trying to tell you is:

Hey fellow ocean explorer, I'm trying to call your showAction(), but I can't figure out what value to pass to genusName because I don't see a {genusName} wildcard in the route. I'm shore you can help me.

As long as those always match, you'll be great.

Listing all Routes

When you load this page, Symfony loops over all the routes in your system and asks them one-by-one: do you match /genus/octopus? Do you match /genus/octopus? As soon as it finds one route that matches that URL, it stops and calls that controller.

So far, we only have one route, but eventually we'll have a lot, organized across many files. It would be swimming if we could get a big list of every route. Ha! We can!

Symfony comes with an awesome debugging tool called the console. To use it, go to the terminal and run

php bin/console

This returns a big list of commands that you can run. Most of these help you with debugging, some generate code and others do things like clear caches. We're interested in debug:router. Let's run that:

php bin/console debug:router

Nice! This prints out every route. You can see our route at the bottom: /genus/{genusName}. But there are other routes, I wonder where those are coming from? Those routes give you some debugging tools - like the little web debug toolbar we saw earlier. I'll show you where these are coming from later.

When we add more routes later, they'll show up here too.

Ok, fun fact! A baby seahorse is called a "fry".

How about a relevant fun fact? You now know 50% of Symfony. Was that really hard? The routing-controller-response flow is the first half of Symfony, and we've got it crossed off.

Now, let's dive into the second half.

Leave a comment!

43
Login or Register to join the conversation
Default user avatar
Default user avatar Arman Pasayandeh | posted 5 years ago

Is it just me who think he is trying to be like Jeff?

42 Reply

Hey Arman Pasayandeh!

I'm not sure what you mean? Who's Jeff? Do you mean Jeffrey Way from Laracasts? If so, that's a great compliment - he does great work :).

Cheers!

2 Reply
Default user avatar
Default user avatar Jaroslav | posted 5 years ago

Hi! Very nice tutorial, just missed the information that I should probably run assets:install in some of previous steps (to get as nice debug page as you get) and now I am in trouble with renaming genusName to genusName2. I dont get any error, just white page with "Genus name: " string. Why I do not get an error page, plase?

1 Reply

Hey @Jaroslav!

Thanks! :). You didn't miss the assets:install - we never ran it - but I believe that the installer handles this for you (it's also run automatically when you run "composer install"). But, if something went wrong, it's possible you need to run this.

But, if you're getting a white screen (white screen of death! I call it) - then that's a different problem. The most likely cause is that you're not in the *dev* environment in Symfony. If you're using the built-in PHP web server that we use (i.e. bin/console server:run) then you will get this automatically. But if you're using your own web server, like Apache or Nginx, then you'll need to modify your URL, e.g. if you setup a host name called mydomain.local, then you would go to http://mydomain.local/app_dev.php for the homepage, or http://mydomain.local/app_dev.php/genus for the /genus page. The extra app_dev.php activates the "dev" environment. We talk about this is the second course of the Symfony series :) http://knpuniversity.com/sc...

Let me know if that's the problem, or if it's something else!

Cheers!

1 Reply
Default user avatar
Default user avatar Terry Caliendo | posted 5 years ago

Is it possible to match routing based on the host name using annotations?

The documentation shows how to do it for YAML, XML, or PHP, but doesn't seem to show a way to do it with annotations.
http://symfony.com/doc/curr...

Reply

Hey Terry,

Yeah, it's 100% possible! Looks like it's a lack in docs. Here's an example:


    /**
     * @Route("/oauth", name="oauth_login", host="login.%domain%")
     */
    public function oAuthLoginAction()
    {
    }

Where domain is a parameter which equals your base domain name like "example.com", so this route will match https://login.example.com/oauth only, check it with "$ bin/console debug:router" Symfony command. Or, of course, you can hardcode host like host="login.example.com" - it's up to you, but then you have some problems with environment like prod/dev/test because probably need different base URLs for them.

Cheers!

Reply
Default user avatar
Default user avatar Terry Caliendo | Victor | posted 5 years ago

Ha. Its pretty obvious, I should have tried that. I kept trying to make an @Host line, I should have thought to add it to the arguments. Thanks much!

Reply
Default user avatar
Default user avatar Terry Caliendo | Victor | posted 5 years ago | edited

Follow up...

My original question was about annotations, but I first wanted to use your suggestion of the the parameter variable in my routing.yml file.

I added the %domain% to my parameters.yml file like so:


parameters:
    domain: mydomain.com

Then I started testing results and things seemed to be routing correctly. So I changed the parameters line to no longer match just to prove to myself what I did was working:


parameters:
    domain: mydomainX.com

And things kept routing well (though they shouldn't have).

But I went to change something as simple as a quote in the routing.yml file and suddenly things stopped routing well.


my_routing_name:
    path: /{Slug1}/{Slug2}
    host: '{UserName}.%domain%'  // changed from single to double quotes

I thought maybe the %domain% variable was being taken as a literal string because of the single quotes.

But when I changed the double quotes back to single quotes, it stayed broken suggesting that the change in quotes may have triggered something to change, but that they themselves weren't the actual issue.

So I figured its a cache issue and commented out the line in my app_dev.php file:


// $kernel->loadClassCache();

That didn't help any with the above as far as consistency.

Thus, I decided to fully clear the cache with the debug command:


./bin/console cache:clear

Suddenly I started getting consistent results. When I set the domain to no longer match and clear the cache, the routing immediately breaks. If I change the domain to match and clear the cache, the routing immediately works again.

Questions:
1) Do single vs double quotes matter for variable interpolation in yml files? I'm seeing it does not matter.

2) Is there any way to totally kill all caching during dev testing such that I don't have to run the cache:clear command in between every change as I work on this?

Reply

Hey Terry,

Wow, good investigation!

1. Yeah, it doesn't matter... but in single quotes you have to escape less things. Check this comment: https://github.com/symfony/... . That's why in that PR double quotes were changed to single ones.

2. Actually, Symfony should regenerate the cache automatically in dev environment, but there're a few cases where it doesn't and you have to clear the dev cache manually. For example when you create a new translation. But maybe the host feature in routes requires it too, probably because developers change it very rare. But IIRC I don't have this problem :/

Cheers!

Reply
Default user avatar
Default user avatar Support | posted 5 years ago

I am not understanding this, I can't get one video that does not show something that works.....it is annoying. I am following exactly what is from the video but each time I get errors. Is this an updated video from the latest symfony version?

Reply

Hi Ben,

No, this tutorial is not updated to the latest Symfony version and mostly uses Symfony components of 2.x. Actually, dependencies locked at v2.7.5, you can see it in our composer.lock file. Could you show us an error you have - we will be glad to help you to fix it.

Cheers!

Reply
Default user avatar

Yes but this is not very nice as it is promoted as "Symfony 3" tutorial, so I have paid a subscription just to find out that it is Symfony 2....Where can I get a refund as all the tutorials are causing me a headache, I am losing too much time trying to figure out what is missing or different. Thank you.

Reply

Hey Support

Maybe you would like to check first our special track for Symfony 3 https://knpuniversity.com/t...

Have a nice day :)

Reply
Default user avatar

Hi Diego, yes that's the problem, it shows Symfony 3 but the videos are Symfony 2. Perhaps you should change the track title to Symfony 2 to not confuse users like myself. Thank you.

1 Reply
Default user avatar
Default user avatar Terry Caliendo | posted 5 years ago | edited

My application lets users decide their username. My goal is to provide home directories based on their username.


www.myapplication.com/JoeSchmoe1
www.myapplication.com/JoeSmiley

My application also has routes like:


www.myapplication.com/admin
www.myapplication.com/myroute1

I can't create a regular expression that's just for the user directories as they have the same potential format as my other routes. I'd like to route based on whether or not the slug is found in a list of usernames.

Thus, I'd like to check the first slug to see if it matches a username in the database. If it does match, I'd like to route to a controller for that handles those requests (i.e. Send the request to the HomeDirectoryAction in a User controller )

If it doesn't match, then I'd like to fall back to the default routing like is shown below.


   /**
     * @Route("/admin", name="admin_route")
     */
    public function adminAction(){
    }

Can you point me in the direction of how I can inject this database driven routing before the default routing in Symfony 3?

Reply

Hey Terry,

Symfony routing system matches routes in serial, so I think you can place this route (which looking for users) at the end, i.e. latest in the list. So if you request existent routes - it will match before handling user names, because I don't think you want that someone user overwrite your "/admin" route with "admin" username :) And also have to add some extra validation for usernames to forbid setting reserved usernames like admin, etc. to prevent unexpected behavior.

P.S. one more tip here, I think you should use YAML syntax to register the username route as the latest one, it's impossible to do with route annotations, but for other you can continue using annotations.

Cheers!

Reply
Default user avatar
Default user avatar Terry Caliendo | Victor | posted 5 years ago

Thanks for the response. I'm a bit confused though... Are you saying to register the user names in routes I should problematically overwrite the app/config/routing.yml file? In other words if a user changes their user name from JoeUser to JoeJimUser, I should have a function that removes the old name from the file and adds the new name?

Reply

Hey Terry,

Not quite so, you have to use a pattern, let me show you an example, which probably you already have:


# app/config/routing.yml
user_personal_page:
    path: /{username}

And that's it! But ensure this route is the last in your route list. I suppose you just have to add it to the end of app/config/routing.yml file to make it the last. Then if you go to the existent route like "/admin", it will match first and you'll go to the admin page. But if there's no existent routes (like /admin, etc.) - the "user_personal_page" will match, since it's the last route in the list. Did you get it?

Cheers!

Reply
Default user avatar
Default user avatar Terry Caliendo | Victor | posted 5 years ago

Ah! Now I get what you are saying! That seems to be an easy way to accomplish it! Thanks for the follow up explanation!

Reply
Default user avatar
Default user avatar Marijan Milovec | posted 5 years ago

With default controller it is working fine on start, but when i remove him and add genuscontroller, it doesnt working, I am using apache,
url http://symtest.dev/app_dev....
error 404 not found on this server

Reply

Hey Marijan Milovec

Can you show me your routing.yml file, please ? I believe you need to update it in order to use GenusController

Cheers!

Reply
Default user avatar
Default user avatar Marijan Milovec | MolloKhan | posted 5 years ago

I have found way to get things work, I have used "use Symfony\Component\Routing\Annotation\Route;", when I use "use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;", it's just not working.

app:
resource: '@AppBundle/Controller/'
type: annotation

this is from routing.yml file, after I have create project I have only followed steps from tutorial, so I didnt change anything else beside things from tutorial.
It's just I am not sure what is difference between those two routing.

Thank you.

Reply

Hey Marijan Milovec

Yeah, you have to be careful when typing your use statements

You can think about this class "Sensio\Bundle\FrameworkExtraBundle\Configuration\Route" as another type of a Route class, a special type that lets you use it via "Annotations"

Cheers!

Reply
Default user avatar
Default user avatar Michael Stratford | posted 5 years ago

I'm a little confused. I successfully completed the last chapter.

Moving onto this one, my code is identical to yours.

The route now looks like: @Route("/genus/{genusName}")
The public function showAction contains the parameter $genusName
and the return response is ('The genus: ' . $genusName)

and yet I'm getting a 404.

Has anything changed in 3.2.2 that would make this happen?

Reply

Hey Michael,

No, it doesn't. Please, ensure you don't have a route prefix for the contoller, i.e. extra @Route() annotation about GenusController class. Btw, you can do $ bin/console debug:router | grep /genus/. Do you really get exactly the /genus/{genusName} path as a result of this command? And of course, don't forget to clear the cache. Keep in mind, that prod cache doesn't regenerate by itself, you should do bin/console cache:clear -e prod each time you change annotations, configuration, etc.

Let me know if it helps.

Cheers!

Reply
Default user avatar
Default user avatar Michael Stratford | Victor | posted 5 years ago

Clearing the cache did the trick. Thank you for your quick and detailed response!

Reply

Great! It works in most cases ;)

Cheers!

Reply

there I just (been taking a break after a long project) found myself facing an issue here with wildcarded routing.

lets assume the following example.

@route("/{something}")

on root /

but if someone wants to go to /admin or /manage or /whatever-else-not-related-to-wildcard yet still having root / as parent.

is there a way to "avoid" being caught on that wildcard on some urls? (without using a ridiculous switch/case forward method to parse the urls. as hardcoding that would be unmaitainable)

Reply

Hey jlchafardet ,

Yes, the easiest workaround for this is make your `@route("/{something}")` route the latest route in your system, then it will be matched *only* when the others didn't.

Cheers!

1 Reply

now, here's the issue, how about different controllers ?

I don't want to have a controller or a method for each {something} which is why I'm using the {something} wildcard.

unless .... I switch to yaml routing .... gota consider that, if I do that, I could force the {something} to be the last route to be checked... I'll read about that.

Or is there a way to tell the router component to check a specific controller last? maybe?

Reply

Hey jlchafardet,

Yes, I see... Hm, I don't know the way to specify a controller to be checked the last, but actually, you need to specify the only one route ("/{something}") in YAML and make it the last - I think it will be enough. I'm not sure, but maybe a custom route loader could help you with your issue too: https://symfony.com/doc/cur... . And take a look at DynamicRouter which is used in Symfony CMF, I suppose it solves your problem in a some way: https://symfony.com/doc/cur...

Cheers!

1 Reply

I'll take a read, I'm not too well documented on howto use 2 types of routers or a custom one or a dymanic one, but thanks for the links, I'll take a read about them and I'll get here eventually

11 Reply
Default user avatar
Default user avatar Brian Baquiran | posted 5 years ago

I'm not getting the error message when the wildcard and variable name don't match, just "The genus: "

Reply

Hey Brian Baquiran !

Hmm... Does your code look exactly like mine, or do you have some differences? For example, if you have {genusName2} in the route, but $genusName as your argument, did you possibly give it a default value (e.g. $genusName = null)? Are you also using annotations routes (or are you possibly using YAML)?

Lemme know - there's gotta be some tiny detail that's mucking things up :).

Cheers!

Reply
Default user avatar
Default user avatar Brian Baquiran | weaverryan | posted 5 years ago

Hi! Yes, the code is exactly what's in the video and script. I'm using annotations, as directed in the tutorial. I haven't set a default value.

I did notice that the first time I tried it, the @Route annotation was autocompleted as Symfony\Component\Routing\Annotation\Route and not the Sensio\Bundle\FrameworkExtraBundle\Configuration\Route in the tutorial. It also worked, and I didn't notice the difference until trying to figure out why it didn't produce the error. I've set it to use the Sensio one, but still no joy.

Anything else I can check?

Reply

Hey Brian!

Yea, good find on changing the annotation use statement for Route. It's one of those situation where there are multiple "Route" classes, so PhpStorm just suggests them all :).

So, 2 things:

A) Can you post your full controller code here?
B) Are you using the built-in PHP web server (e.g. bin/console server:run)? If not, and you're using your own web-server, make sure to prefix the URL with app_dev.php. So, instead of going to http://mysite.local/genus/octopus, go to http://mysite.local/app_dev.php/genus/octopus. This activates the "dev" environment (which is activated automatically if you use the built-in web server). If that's the problem - we have more on that in the next screencast :) http://knpuniversity.com/sc...

Cheers!

Reply
Default user avatar
Default user avatar Brian Baquiran | weaverryan | posted 5 years ago | edited

Hey weaverryan,
You can check it here: https://github.com/brianbaq...

I did a dump($genusName) and Symfony displays it as null.

I am using bin/console server:run.

In the Symfony Profiler's Request/Response screen, _route_params shows "genusName2" => "octopus"

It's not stopped me from working through the rest of the course, but it bothers me that it's not producing an error when it should.

Reply

Hey Brian!

Wow, so this blew my mind a little bit :). And after some digging, you seem to have uncovered a bug recently introduced into Symfony. I've created an issue about it here - https://github.com/symfony/... - we'll find out if this is an intended change or not. But, the most important thing is that you are NOT crazy, in fact, you did a great job of following through on this very weird behavior.

So, keep going on the tutorial, and don't let this nag you anymore. When I first tried your code, I thought I was losing my mind!

Cheers!

Reply
Default user avatar
Default user avatar Brian Baquiran | weaverryan | posted 5 years ago | edited

Haha, I guess that's my initial contribution to the platform :D

Thanks weaverryan!

Reply
Default user avatar
Default user avatar Enkhbilguun E. | posted 5 years ago

Hi,
How can we set a default value for the $genusName variable if http://domain/genus/{genusName} is empty?

Reply

Hey Enkhbilguun E.!

Good question :). If you're using annotation routes (like in this course), give you argument a default value:


public function showAction($genusName = 'foo')

When you do that, when you go to http://domain/genus, it will now match your route and use this default.

Cheers!

Reply
Default user avatar
Default user avatar Konrad Zając | posted 5 years ago

Hi,about this use argument/
I heve these two use statements:

use Symfony\Component\BrowserKit\Response

use Symfony\Component\HttpFoundation\Response;
when I use symfonys autocompletion - I get the first one.
But the second one is the one that works, could somone explain?
-----edit-----
sometimes it's the other way:
use Symfony\Component\HttpKernel\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
symfony gave me the first, but the second one works

Reply

Hi Konrad!

Apologies for my slow reply on your comments :). But now I've answered this question over on another comment: https://knpuniversity.com/s...

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0" // v3.1.3
    }
}
userVoice