The definitive guide of Symfony 1.2

9.4. Routing Configuration

The routing system does two things:

  • It interprets the external URL of incoming requests and transforms it into an internal URI, to determine the module/action and the request parameters.
  • It formats the internal URIs used in links into external URLs (provided that you use the link helpers).

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/*

9.4.1. Rules and Patterns

Routing rules are bijective associations between an external URL and an internal URI. A typical rule is made up of the following:

  • A unique label, which is there for legibility and speed, and can be used by the link helpers
  • A pattern to be matched (url key)
  • An array of request parameter values (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.

9.4.2. Pattern Constraints

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.

9.4.3. Setting Default Values

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.

9.4.4. Speeding Up Routing by Using the Rule Name

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:

  • The formatting of internal URIs is done faster, since symfony doesn't have to browse all the rules to find the one that matches the link. In a page with a great number of routed hyperlinks, the boost will be noticeable if you use rule labels instead of module/action pairs.
  • Using the rule label helps to abstract the logic behind an action. If you decide to change an action name but keep the URL, a simple change in the routing.yml file will suffice. All of the link_to() calls will still work without further change.
  • The logic of the call is more apparent with a rule name. Even if your modules and actions have explicit names, it is often better to call @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.

9.4.5. Adding an .html Extension

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 }

9.4.6. Creating Rules Without routing.yml

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 since 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_2/) 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.