Ian P. Christian's Personal Blog Random witterings from pookey

29Mar/094

Playing with symfony routing – without symfony

symfony 1.2 was quite an improvement over 1.0, one of the most important changes was improved decoupling between components. In this article we're going to put this to the test and have some fun with sfRouting.

Before touching symfony code, I will quickly demonstrate how to use .htaccess to redirect all requests thought your Front Controller. In this example I have a PHP file called test.php in my webroot, which i want to use for all requests. Here is my .htaccess file:

<IfModule mod_rewrite.c>
  RewriteEngine On
 
  RewriteRule ^(.*)$ test.php [QSA,L]
</IfModule>

My test.php file simply contains the following:

<?php
 
echo $_SERVER["REDIRECT_URL"] . '<br />';
echo $_SERVER["REDIRECT_QUERY_STRING"] . '<br />';
echo $_SERVER["QUERY_STRING"] . '<br />';
echo $_SERVER["REQUEST_URI"] . '<br />';

To test this is working hitting a URL like this, http://website/moocow/fish?spoons=3, should result in output as follows:

/moocow/fish
spoons=3
spoons=3
/moocow/fish?spoons=3

We can see that we have all the data we need to route requests however we might choose within our application. So lets get started with an sfRoute! My test code isn't going to use symfony's autoloader, so we need to use require statements to pull in the symfony code. I've modified my test.php to create a couple of sfRoute objects to demonstrate how they work

<?php
 
require_once('../lib/vendor/symfony/lib/routing/sfRoute.class.php');
 
echo '<pre>';
$route = new sfRoute('/moocow/:animal');
$ret = $route->matchesUrl($_SERVER["REDIRECT_URL"]);
var_dump($ret);
 
$route = new sfRoute('/:ribbit/:animal');
$ret = $route->matchesUrl($_SERVER["REDIRECT_URL"]);
var_dump($ret);

Revisiting the URL used above, http://website/moocow/fish?spoons=3, we can see that the sfRoute matched the URL, and returned an array containing the parameters:

array(1) {
  ["animal"]=>
  string(4) "fish"
}
array(2) {
  ["ribbit"]=>
  string(6) "moocow"
  ["animal"]=>
  string(4) "fish"
}

This shows how a sfRoute object can take a URI and process it, extracting out placeholder variables and return an array of the values. If we were to build an array of sfRoute objects, we could iterate though them all trying the URL with each until we found one that matched. Alternatively, we can use sfPatternRouting, which effectively does this for us. Here is my new test.php file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$sfpath = '../lib/vendor/symfony/lib/';
require_once($sfpath.'routing/sfRoute.class.php');
require_once($sfpath.'routing/sfRouting.class.php');
require_once($sfpath.'routing/sfPatternRouting.class.php');
require_once($sfpath.'event/sfEvent.class.php');
require_once($sfpath.'event/sfEventDispatcher.class.php');
 
$routing = new sfPatternRouting(new sfEventDispatcher());
$routing->connect('home', new sfRoute('/'));
$routing->connect('hello_world', new sfRoute('/hello/:name', array(
  'action' => 'hello',
  'module' => 'main',
)));
$routing->connect('default', new sfRoute('/:module/:action'));
 
$ret = $routing->parse($_SERVER["REQUEST_URI"]);
foreach($ret as $key => $val)
{
  if (!is_object($val))
  {
  echo "{$key} :: {$val} </br >";
  }
}

There's a couple of things to take note of here. Line 12 sets default values on a route, in this case defining a module and an action. sfPatternRouting is a little more symfony specific in that it has the concept of 'action', and 'module' - but it's still easily usable within your own applications. The second thing to notice is on line 21, where we exclude objects from our output - this is because the parse() method will return an array containing an element keyed _sf_route which references the sfRoute object that was matched. We don't want this causing messy output, so we simply exclude it.

Below is the result of hitting the new site with a couple of URLs:

http://website/hello/pookey
--
module :: main 
action :: hello 
name :: pookey 
 
http://website/foo/bar
--
module :: foo 
action :: bar

This introduction is very simple, and doesn't really touch on the full power of symfony's routing. I strongly advise you read the API docs, the routing unit tests and of course the routing source code. Hopefully it's given a sufficient introduction that both gives symfony users an insight into how the routing works under the hood, and non-symfony users ideas on how they might use routing within other projects.

Filed under: geek, php, symfony Leave a comment
Comments (4) Trackbacks (0)
  1. Interesting article.

    May I suggest that swapping around lines 13 and 14 from your final code sample would make it easier to read…?

  2. Very cool – we should see more of Symfony components being used outside of Symfony. First, the fact that Symfony’s components have been increasingly designed to work without Symfony has very powerful potential. Second – seeing a component used outside of its normal Symfony shell is a great way to understand much better how it really works.

    Very nice.

  3. In a recent post about Symfony components – Fabien wrote about how the Symfony project (yes, that’s right – a capital S now!) will be releasing more and more components. In this post I introduce sfYaml, a YAML parser for PHP – building on my previous

  4. Excellent article! I was just banging my head trying to figure this one out when I found your post.

    I’d like to add my two cents to your post.

    1) Note that $routing->parse() will return false if no routes match so a special default or 404 case should be set up somehow to avoid error messages.

    2) Don’t forget the generate() method for the sfRoute class which generates a pretty URL based on the route, for example:

    $route = new sfRoute(‘/:module/:action/:language/page/:page’);
    echo $route->generate(array(
    ‘module’ => ‘myModule’,
    ‘action’ => ‘myAction’,
    ‘language’ => ‘en’,
    ‘page’ => 3
    ));

    // /myModule/myAction/en/page/3


Leave a comment

No trackbacks yet.