The routing system does two things:
The conversion is based on a set of routing rules . These rules are stored in a routing.yml configuration file located in the application config/ directory. Listing 9-15 shows the default routing rules, bundled with every symfony project.
Listing 9-15 - The Default Routing Rules, in frontend/config/routing.yml
# default rules
homepage:
url: /
param: { module: default, action: index }
default_symfony:
url: /symfony/:action/*
param: { module: default }
default_index:
url: /:module
param: { action: index }
default:
url: /:module/:action/*
Routing rules are bijective associations between an external URL and an internal URI. A typical rule is made up of the following:
url key)param key)Patterns can contain wildcards (represented by an asterisk, *) and named wildcards (starting with a colon, :). A match to a named wildcard becomes a request parameter value. For instance, the default rule defined in Listing 9-15 will match any URL like /foo/bar, and set the module parameter to foo and the action parameter to bar. And in the default_symfony rule, symfony is a keyword and action is named wildcard parameter.
NOTE New in symfony 1.1 named wildcards can be separated by a slash or a dot, so you can write a pattern like:
my_rule:
url: /foo/:bar.:format
param: { module: mymodule, action: myaction }
That way, an external URL like 'foo/12.xml' will match my_rule and execute mymodule/myaction with two parameters: $bar=12 and $format=xml.
You can add more separators by changing the segment_separators parameters value in the sfPatternRouting factory configuration (see chapter 19).
The routing system parses the routing.yml file from the top to the bottom and stops at the first match. This is why you must add your own rules on top of the default ones. For instance, the URL /foo/123 matches both of the rules defined in Listing 9-16, but symfony first tests my_rule:, and as that rule matches, it doesn't even test the default: one. The request is handled by the mymodule/myaction action with bar set to 123 (and not by the foo/123 action).
Listing 9-16 - Rules Are Parsed Top to Bottom
my_rule:
url: /foo/:bar
param: { module: mymodule, action: myaction }
# default rules
default:
url: /:module/:action/*
NOTE
When a new action is created, it does not imply that you must create a routing rule for it. If the default module/action pattern suits you, then forget about the routing.yml file. If, however, you want to customize the action's external URL, add a new rule above the default one.
Listing 9-17 shows the process of changing the external URL format for an article/read action.
Listing 9-17 - Changing the External URL Format for an article/read Action
<?php echo url_for('article/read?id=123') ?> => /article/read/id/123 // Default formatting // To change it to /article/123, add a new rule at the beginning // of your routing.yml article_by_id: url: /article/:id param: { module: article, action: read }
The problem is that the article_by_id rule in Listing 9-17 breaks the default routing for all the other actions of the article module. In fact, a URL like article/delete will match this rule instead of the default one, and call the read action with id set to delete instead of the delete action. To get around this difficulty, you must add a pattern constraint so that the article_by_id rule matches only URLs where the id wildcard is an integer.
When a URL can match more than one rule, you must refine the rules by adding constraints, or requirements, to the pattern. A requirement is a set of regular expressions that must be matched by the wildcards for the rule to match.
For instance, to modify the article_by_id rule so that it matches only URLs where the id parameter is an integer, add a line to the rule, as shown in Listing 9-18.
Listing 9-18 - Adding a Requirement to a Routing Rule
article_by_id:
url: /article/:id
param: { module: article, action: read }
requirements: { id: \d+ }
Now an article/delete URL can't match the article_by_id rule anymore, because the 'delete' string doesn't satisfy the requirements. Therefore, the routing system will keep on looking for a match in the following rules and finally find the default rule.
Note that as articles will be retrieved by slug, you should add an index to the slug column in the Article model description to optimize database performance.
You can give named wildcards a default value to make a rule work, even if the parameter is not defined. Set default values in the param: array.
For instance, the article_by_id rule doesn't match if the id parameter is not set. You can force it, as shown in Listing 9-19.
Listing 9-19 - Setting a Default Value for a Wildcard
article_by_id:
url: /article/:id
param: { module: article, action: read, id: 1 }
The default parameters don't need to be wildcards found in the pattern. In Listing 9-20, the display parameter takes the value true, even if it is not present in the URL.
Listing 9-20 - Setting a Default Value for a Request Parameter
article_by_id:
url: /article/:id
param: { module: article, action: read, id: 1, display: true }
If you look carefully, you can see that article and read are also default values for module and action variables not found in the pattern.
TIP
You can define a default parameter for all the routing rules by calling the sfRouting::setDefaultParameter() method. For instance, if you want all the rules to have a theme parameter set to default by default, add $this->context->getRouting()->setDefaultParameter('theme', 'default'); to one of your global filters.
The link helpers accept a rule label instead of a module/action pair if the rule label is preceded by an 'at' sign (@), as shown in Listing 9-21.
Listing 9-21 - Using the Rule Label Instead of the Module/Action
<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?> // can also be written as <?php echo link_to('my article', '@article_by_id?id='.$article->getId()) ?>
There are pros and cons to this trick. The advantages are as follows:
routing.yml file will suffice. All of the link_to() calls will still work without further change.@display_article_by_slug than article/display.On the other hand, a disadvantage is that adding new hyperlinks becomes less self-evident, since you always need to refer to the routing.yml file to find out which label is to be used for an action.
The best choice depends on the project. In the long run, it's up to you.
TIP
During your tests (in the dev environment), if you want to check which rule was matched for a given request in your browser, develop the "logs and msgs" section of the web debug toolbar and look for a line specifying "matched route XXX". You will find more information about the web debug mode in Chapter 16.
NOTE New in symfony 1.1 Routing operations are much faster in production mode, where conversions between external URLs and internal URIs are cached.
Compare these two URLs:
http://myapp.example.com/article/Finance_in_France http://myapp.example.com/article/Finance_in_France.html
Even if it is the same page, users (and robots) may see it differently because of the URL. The second URL evokes a deep and well-organized web directory of static pages, which is exactly the kind of websites that search engines know how to index.
To add a suffix to every external URL generated by the routing system, change the suffix value in the application settings.yml, as shown in Listing 9-22.
Listing 9-22 - Setting a Suffix for All URLs, in frontend/config/factories.yml
prod:
routing:
param:
suffix: .html
The default suffix is set to a period (.), which means that the routing system doesn't add a suffix unless you specify it.
It is sometimes necessary to specify a suffix for a unique routing rule. In that case, write the suffix directly in the related url: line of the routing.yml file, as shown in Listing 9-23. Then the global suffix will be ignored.
Listing 9-23 - Setting a Suffix for One URL, in frontend/config/routing.yml
article_list:
url: /latest_articles
param: { module: article, action: list }
article_list_feed:
url: /latest_articles.rss
param: { module: article, action: list, type: feed }
As is true of most of the configuration files, the routing.yml is a solution to define routing rules, but not the only one. You can define rules in PHP, either in the application config.php file or in the front controller script, but before the call to dispatch(), because this method determines the action to execute according to the present routing rules. Defining rules in PHP authorizes you to create dynamic rules, depending on configuration or other parameters.
The object that handles the routing rules is the sfPatternRouting factory. It is available from every part of the code by requiring sfContext::getInstance()->getRouting(). Its prependRoute() method adds a new rule on top of the existing ones defined in routing.yml. It expects four parameters, which are the same as the parameters needed to define a rule: a route label, a pattern, an associative array of default values, and another associative array for requirements. For instance, the routing.yml rule definition shown in Listing 9-18 is equivalent to the PHP code shown in Listing 9-24.
NOTE
New in symfony 1.1: The routing class is configurable in the factories.yml configuration file (to change the default routing class, see chapter 17). This chapter talks about the sfPatternRouting class, which is the routing class configured by default.
Listing 9-24 - Defining a Rule in PHP
sfContext::getInstance()->getRouting()->prependRoute( 'article_by_id', // Route name '/article/:id', // Route pattern array('module' => 'article', 'action' => 'read'), // Default values array('id' => '\d+'), // Requirements );
The sfPatternRouting class has other useful methods for handling routes by hand: clearRoutes(), hasRoutes() and so on. Refer to the API documentation (http://www.symfony-project.org/api/1_1/) to learn more.
TIP Once you start to fully understand the concepts presented in this book, you can increase your understanding of the framework by browsing the online API documentation or, even better, the symfony source. Not all the tweaks and parameters of symfony can be described in this book. The online documentation, however, is limitless.