New in symfony 1.2: Toward a RESTful architecture (Part 1)

Yesterday, I have committed the first slew of changes to the routing framework. Thanks to this refactoring, developers have new opportunities to customize the routing and this will allow very cool features in the very near future.

But today, let's dive into the goodness of the symfony 1.2 routing framework.

Routes as first-class objects

Before symfony 1.2, the routing system (via the sfPatternRouting class) stored its routes as an associative array. It works quite well but does not allow for easy customization. To give developers the freedom to easily enhance the routing process, the routes are now stored as an array of sfRoute objects.

This refactoring is totally backward compatible and nothing need to be changed in your routing.yml configuration file.

If you connect your routes with PHP code, you must now pass a sfRoute instance as the second argument to the connect(), preprendRoute(), appendRoute(), and insertRouteBefore() methods:

$routing->connect('foo_bar', new sfRoute('/foo/:bar', array('module' => 'foo', 'action' => 'bar')));
 

Route customization

The sfRoute constructor takes an array of options as its last argument to allow the customization of each route. In the routing.yml configuration file, use to options key to override defaults:

article:
  url:     /article/:id-:slug
  options: { segment_separators: [/, ., -] }
 

The segment_separators option sets the characters that can separate each segment of the pattern. In the previous example, we explicitly add the - (dash) character as a valid separator to the two default built-in separators (/ and .). This allows a URL like /article/1-my_article_title to match the route with an id variable of 1 and a slug variable of my_article_title.

This option was already available in symfony 1.1, but you were only allowed to change it globally. By adding new separators globally, you could have potentially broken third-party routes (like the ones defined in plugins).

Beside the options already available, there are two new options:

  • generate_shortest_url: Whether to generate the shortest URL possible
  • extra_parameters_as_query_string: Whether to generate extra parameters as a query string

These options can be configured globally for the routing or locally for each route. By default, they are set to false in factories.yml to keep backward compatibility with previous symfony versions.

Let's see how to use these options to customize a route:

articles:
  url:     /articles/:page
  param:   { module: article, action: list, page: 1 }
  options: { generate_shortest_url: true }
 

This route will generate the shortest URL possible. So, if you pass a page of 1, which is the default value for the page variable, the generated URL will be /articles:

echo url_for('@articles?page=1'); // generates /articles
// would have been /articles/1 in symfony 1.1
 
echo url_for('@articles?page=2'); // generates /articles/2
 

Let's take another example to illustrate extra_parameters_as_query_string usage:

articles:
  url:     /articles
  options: { extra_parameters_as_query_string: true }
 

This route will accept extra parameters that are not valid variables for the route and add them as a query string:

echo url_for('@articles?page=1'); // generates /articles?page=1
// would not have matched the route in symfony 1.1
 
echo url_for('@articles?page=2'); // generates /articles?page=2
 

As this option can change the matching route depending on your configuration, enable it with care if you are upgrading an existing project. But as the test browser uses the routing process, you will just have to launch your functional test suite to check if it breaks something.

Routing customization

All the logic from the sfPatternRouting routing class has been moved to the sfRoute object:

  • When an HTTP request comes in, the routing object asks each route in turn if it matches the URL.
  • And when you want to generate a URL, the routing object asks each route in turn if it is able to generate a URL for the given parameters.

By embarking all the logic in the route class, it is just a matter of creating a new route class to change the behavior of the parsing or generation process.

If you want to change the default route class used by a route, add a class key to your route configuration like this:

article:
  url:   /article/:id
  param: { module: article, action: index }
  class: myRoute
 

With this routing configuration, symfony will use the myRoute class for the article route, instead of the built-in sfRoute class. It is now up to you to override the default behaviors.

The sfRoute class is much more modular than the old sfPatternRouting class to allow easier customization of the default behavior. The "compilation" phase has been refactored into smaller methods, the code has been simplified, and it is now based on a "real" tokenizer.

The built-in sfRequestRoute

Symfony has another built-in route, sfRequestRoute, which can enforce the HTTP method during the matching process:

article:
  url:          /article/:id
  requirements: { sf_method: get }
  class:        sfRequestRoute
 

With the previous routing configuration, the article route will only match requests with a GET HTTP method.

If you define several routes with the same url but different method requirements, you can pass sf_method as a parameter when you generate a route:

<?php echo link_to('Great article', '@article?id=1&sf_method=get')) ?>
 

This is made possible because the routing is now aware of the request context. When the request calls the routing, it passes the following context:

  • method: The HTTP method
  • format: The request format
  • host: The hostname
  • is_secure: Whether the request was called with HTTPS or not
  • request_uri: The full request URI
  • prefix: The prefix to add to each generated route

The sfRequestRoute is the first step towards a RESTful architecture.

What's next?

In the next part, we will see how symfony manages resources by automatically generating RESTful routes based on a simple configuration of routing.yml. We will also learn how to cut the code you have to write in your actions, thanks to the built-in integration of Propel and Doctrine.

Last but not least, I will talk about the new routing framework at symfonyCamp with even more examples and a live demo. So, if you want to learn more about symfony 1.2 and if you are not already registered, you can still join the 60 people who will be there to share their symfony experience.

If you want to follow the progress of symfony 1.2 or if you want to know all the changes we have made for symfony 1.2, you can periodically check the upgrade to symfony 1.2 page where all changes and news features are documented in real-time.

Comments

great addition!
Love the way to put GET parms in the URLs, the new routing changes sound great.

Well done :)
Wahou that's awesome :)

Fabien, do you speak about Symfony 1.2 at Forum PHP Paris in december ?
Sounds great, i like the ability to choose to have get parameters or "routing parameters"
Is that to say, in Symfony 1.2, it is easy to make our program into REST architecture by the new routing systems?
extra_parameters_as_query_string is what I was waiting for a long time and was making me add extra routes for the fact we could not add extra parameters for routes. Nice one.
How these changes will impact the performance?
Malas: your developping performances will be greatly improved.
@Malas: The routes objects are cached. And in the near future, we will only unserialize the route objects we need. So, it will perform very well.
I am eagerly awaiting part 2. Any word on when that'll be?

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.