Showing posts with label Symfony. Show all posts
Showing posts with label Symfony. Show all posts

Friday, June 20, 2008

Symfony Routing Tutorial

09. Apr. 2009: This was my first post, and is somewhat lacking quality in retrospect. As it's still generating a lot of traffic, I'd appreciate your opinion on how it could be improved. Thanks!

There are plenty of nice tutorials out there describing the installation and usage of Symfony, including the official Askeet Tutorial and the Symfony Book available online.

When creating my first project in Symfony, however, I was stumped on how to get the module I wanted to load, giving it the parameters I wanted, setting the default module/action and stuff like that. If you're in the same boots, this is for you.

First things first

I'm going to assume that you know how to set up a project, an application and a module. If not, you can find how to do these in the docs mentioned above.
Note: the default 'module successfully created' page has nothing to do with routing. In fact, when you see that, you already got to the page you wanted to, but the default executeIndex action redirects you to this page. So go change it ($SF_ROOT_DIR/apps/$APP_NAME/modules/$MODULE_NAME/actions/actions.class.php)

The Project

We'll need an example to see how routing in Symfony works. How about... a calendar? A calendar that shows the current month when you don't give it any parameters, and the appropriate month when you give it a YYYY/MM date. Thanks to the sfWebRequest class we'll be able to get the year specified, and set a default value in case we didn't get one in just one line. Same goes for the month. Let's say our indexSuccess.php template takes care of rendering the the calendar, we just have to give it the year and month to show.
So now we have a module called 'calendar', and an action called index - which is the default action, by default. More on that later. The actions.class.php could look like this:

<?php
/**
* naptar actions.
* comments stripped
*/
class naptarActions extends sfActions
{
  
/**
   * Executes index action
   *
   */
  
public function executeIndex()
  {
    
$this->year $this->getRequest()->getParameter('year'date('Y'));
    
$this->month $this->getRequest()->getParameter('month'date('n'));
  }
}
?> 

This warrants some explanation. The variables we define as instance variables in the action ($this->foo) are available in the template as local variables ($foo). That's just what we need for the calendar.
The second argument to getParameter is the default value. date('Y') gives the current year as YYYY, while date('n') gives the current month as MM. See the php manual for a complete description.

routing.yml

Believe it or not, we're almost done. We just need to tell Symfony to route the appropriate requests here, to the module calendar, action index. We do this by editing the $SF_ROOT_DIR/apps/$APP_NAME/config/routing.yml.
Let's see the first case: the url is something like http://www.foo.com/index.php/calendar or just http://www.foo.com/calendar if the server has mod_rewrite compiled into / installed.

What the routing system of Symfony sees is '/calendar'. This matches one of the default lines in routing.yml:

default_index:
  url:   /:module
  param: { action: index }

What this tells Symfony is 'If you get only a module name, then you have to run the 'index' action of that module'. Here index is defined as the default action to execute when it's not explicitly specified.
The URL part after the colon here is considered the name of the parameter, while that part of the actual URL will be the value of the parameter. For example if the definition "/:module/:action/:year/:month" is applied to the URL "/calendar/index/2008/06", an sfWebRequest object somewhere deep in Symfony will have this array:

$params = {
            
"module" => "calendar",
            
"action" => "index",
            
"year"   => "2008",
            
"month"  => "06"
          
}

The next, optional line in the routing definition is param: it's an associative array which will be passed on as if it was included in the URL. Getting any ideas yet? :)

The fun part

Back to our example. We still have to deal with the second case, when the end of the URL is like "/calendar/2008/06".
What do we have here? First, the module name is in the URL, so we could just write /:module in our definition. However, if we do so, this rule will apply to any module with two more arguments after it. So we'll have to say /calendar explicitly. This in turn means that we'll have to include the module to use in the param line. As this module has only one action, we don't want to have it in the URL - it's not elegant to have unnecessary information in there. Right, so we'll also have to tell Symfony the action to launch. The year and month, on the other hand, will be from the URL. All that said, the definition we need is this:

calendar:
 url:      /calendar/:year/:month
 param:    { module: 'calendar', action: 'index' }

And that's it. Beautiful in its simplicity, as they say.

I hope this tutorial was useful. Suggestions and comments are of course welcome.