Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "routing"

Default trailing slash for simple routes

Here is my solution for trailing slash in simple localized urls.

In routing.yml add:

homepage_lang:
  url:   /:sf_culture:sf_slash
  param: { module: main, action: index }
  requirements:
    sf_culture: (?:pl|en|de)
    sf_slash: '\/?'
  options:
    suffix: ''
    segment_separators: []
 

Both urls will work now: '/en' & '/en/'

But if you try to use link_to helper

<?php
echo link_to('Homepage', '@homepage_lang');
 

It will require sf_slash parameter.

Notice: sf_culture will be set automagically from current user culture unless you add to route: '@homepage_lang?sf_culture=de'.

There are (at least) 2 solutions for this. First is to add default sf_slash parameter in routing:

homepage_lang:
  url:   /:sf_culture:sf_slash
  param: { module: main, action: index, sf_slash: '' }
  requirements:
    sf_culture: (?:pl|en|de)
    sf_slash: '\/?'
  options:
    suffix: ''
    segment_separators: []
 

And everything will work just fine, link_to() helper will generate urls like: '/en', '/de' etc.

But what if we want trailing slash to be added every time we generate a link? It's more difficult and it's our 2nd solution for required parameter.

Routing rule should look like in the first example:

homepage_lang:
  url:   /:sf_culture:sf_slash
  param: { module: main, action: index }
  requirements:
    sf_culture: (?:pl|en|de)
    sf_slash: '\/?'
  options:
    suffix: ''
    segment_separators: []
 

But we need set sf_flash somehow. To do so, I used filters (was suggested in symfony book 1.2, chapter 9).

Create slashFilter in applications lib directory (app/lib/slashFilter.class.php):

<?php
 
class slashFilter extends sfFilter
{
   public function execute($filterChain)
   {
      $this->getContext()->getRouting()->setDefaultParameter('sf_slash', '/');
      $filterChain->execute();
   }
}
 

And add it to filters.yml:

# You can find more information about this file on the symfony website:
# http://www.symfony-project.org/reference/1_4/en/12-Filters
 
rendering: ~
security:  ~
 
# insert your own filters here
slashFilter:
  class: slashFilter
 
cache:     ~
execution: ~
 

Eventually clear your cache and trailing slash will be added to @homepage_lang route.

But there can be another problem and your slash / will be added as '%2F'. You can play with .htaccess or server configuration but I just created my own link_to() or url_for() helpers which replaces '%2F' with '/' like:

<?php
$url = str_replace('%2F', '/', $url);
 

Any improvements are appreciated ;)

by rozwell on 2010-10-03, tagged routing  slash  trailing 

How to generate routes (links) from the model or from a task

The symfony routing system is tied to the sfContext resource via the factory handler.

It might an issue if you need to build routes in the cli mode, where sfContext has no instance. Sample: one of your cron jobs sends emails that contain links.

The best way I've found is to provide an access to the routing system linked to the ProjectConfiguration class. Add a static protected "$routing" variable and a static public method "getRouting()" :

class ProjectConfiguration extends sfProjectConfiguration
{
  static protected
    $routing = null # Symfony routing system
    ;
 
 
  /**
   * Returns the routing resource for the active application
   */
  static public function getRouting()
  {
    if (null !== self::$routing)
    {
      return self::$routing;
    }
 
    // If sfContext has an instance, returns the loaded routing resource
    if (sfContext::hasInstance() && sfContext::getInstance()->getRouting())
    {
      self::$routing = sfContext::getInstance()->getRouting();
    }
    else
    {
      // Initialization
      if (!self::hasActive())
      {
        throw new sfException('No sfApplicationConfiguration loaded');
      }
      $appConfig = self::getActive();
      $config = sfFactoryConfigHandler::getConfiguration($appConfig->getConfigPaths('config/factories.yml'));
      $params = array_merge(
        $config['routing']['param'],
        array(
          'load_configuration' => false,
          'logging'            => false,
          'context'            => array(
            'host'      => sfConfig::get('app_host',   'localhost'),
            'prefix'    => sfConfig::get('app_prefix', sfConfig::get('sf_no_script_name') ? '' : '/'.$appConfig->getApplication().'_'.$appConfig->getEnvironment().'.php'),
            'is_secure' => sfConfig::get('app_host',   false),
          ),
        )
      );
      $handler = new sfRoutingConfigHandler();
      $routes = $handler->evaluate($appConfig->getConfigPaths('config/routing.yml'));
      $routeClass = $config['routing']['class'];
      self::$routing = new $routeClass($appConfig->getEventDispatcher(), null, $params);
      self::$routing->setRoutes($routes);
    }
 
    return self::$routing;
  }
}
 

In the method, the 'context' parameter needs further explanations.

In a classic context, symfony is loaded after a web request. Sample: https://www.example.com/frontend_dev.php/products

This request provides data that are reused when sf needs to generate a new dynamic url:

In the cli mode, as Symfony is not loaded by a request, you have to provide these parameters (prefix can be guessed).

In app.yml, add:

all:
  host: www.example.com
  is_secure: false
 

Now, let's see how to generate the link. If your routing.yml file looks like it:

product_show:
  url:     /products/:category_slug/:slug
  class:   sfDoctrineRoute
  options: { model: Product, type: object }
  # ...
 

Then building the link is as simple as:

class Product extends BaseProduct
{
  /**
   * Returns the public url
   */
  public function getUrl()
  {
    return ProjectConfiguration::getRouting()->generate('product', $this, true);
  }
 
 
  /* ... */
}
 

Regards.

by Éric Rogé on 2009-12-11, tagged cli  routing 
(2 comments)

Switch culture from any page of your site and improve SEO

CONTEXT

Symfony internationalization is normally managed using session variables. To switch culture you can visit a page that first changes you session variable and then send you back to the page you were visiting, but this potentially breaks your SEO.

Adding culture in every URL is a better solution, but if you want to go back to the page you were visiting, you have to deal with redirection by yourself.

This widget solves the redirection issue by creating localized urls.

CODE

Save your file here: apps/appname/lib/helper/I18nUrlHelper.php

/*
 * Generate a localized version for the URL you are visiting.
 */
 
function localized_current_url($sf_culture = null)
{
  if (! $sf_culture)
  {
    throw new sfException(sprintf('Invalid parameter $sf_culture "%s".', $sf_culture));
  }
 
  $routing    = sfContext::getInstance()->getRouting();
  $request    = sfContext::getInstance()->getRequest();
  $controller = sfContext::getInstance()->getController();
 
  // depending on your routing configuration, you can set $route_name = $routing->getCurrentRouteName()
  $route_name = '';
 
  $parameters = $controller->convertUrlStringToParameters($routing->getCurrentInternalUri());
  $parameters[1]['sf_culture'] = $sf_culture;
 
  return $routing->generate($route_name, array_merge($request->getGetParameters(), $parameters[1]));
}
 

EXAMPLE

Obviously, you must add the :sf_culture token in every rule of your application routing.yml:

product_page:
  url:   /:sf_culture/product/:id
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: product, action: show }
 
homepage:
  url:   /:sf_culture
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: page, action: home }

default_symfony:
  url:   /symfony/:sf_culture/:action/*
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: default }
 
default_index:
  url:   /:sf_culture/:module
  param: { action: index }
  requirements: { sf_culture: (?:en|es|fr|it|de) }
 
default:
  url:   /:sf_culture/:module/:action/*
  requirements: { sf_culture: (?:en|es|fr|it|de) }
 

This code let you create a list of link to the localized versions. The list of languages/cultures is taken from your config file.

<div>
  <?php foreach(sfConfig::get('app_languages_available') as $language) { ?>
    <a href="<?php echo localized_current_url($language); ?>"><?php echo $language; ?></a>  
  <?php } ?>
</div>
 
by Davide Fedrigo on 2009-09-28, tagged culture  helper  i18n  navigation  routing 
(3 comments)

Change app by routing class

Create a file into lib/routing/sfChangeAppRoute.class.php and paste :

<?php
 
class sfChangeAppRoute extends sfRoute{
 
    public function generate($params, $context = array(), $absolute = true)
  {
 
    if(!array_key_exists('app',$params))
        return parent::generate($params, $context, $absolute);
 
 
    $app_name=sfContext::getInstance()->getConfiguration()->getApplication();
    $env_name=sfContext::getInstance()->getConfiguration()->getEnvironment();
    if($env_name=='prod')
        $env='';
    else
        $env='_'.$env_name;
 
    $url='/../'.$params['app'].$env.'.php';
 
    return $url;
  }
 
}
 
?>
 

Now into your routing.yml

app:
  url: /application/:app
  params: {  }
  class:  sfChangeAppRoute
 

And after that you could use the route @app?app=MY_other_app

by excessive demon on 2009-08-26, tagged application  routing  url 
(1 comment)

Case-insensitive URLs/routes

This works for symfony 1.2 and might be backwards compatible with 1.1 and 1.0, but I haven't tested it with those versions.

Place this file in an appropriate directory (eg. apps/myapp/lib/routing):

sfRegexExtendedRoute.class.php:

<?php
 
class sfRegexExtendedRoute extends sfRoute
{
  protected function compile()
  {
    $return = parent::compile();
 
    // Case-insensitive matching (default symfony behaviour = NOT case-insensitive).
    if (isset($this->options['case_sensitive']) && !$this->options['case_sensitive'])
    {
      // Make sure $this->regex is in the form "#...#[???]"
      // and that [???] does not already include "i" (the case-insensitive flag).
      if (($parts = explode('#', strrev($this->regex)))
          && 3 <= ($num_parts = count($parts))
          && '' == $parts[$num_parts-1]
          && false === strpos($parts[0], 'i'))
      {
        $this->regex .= 'i';
      }
    }
 
    return $return;
  }
}
 

Add the option case_sensitive: false in the route you want to make case-insensitive and set its class to sfRegexExtendedRoute:

apps/myapp/config/routing.yml:

...
my_route:
  url:      /my/case/insensitive/url/:whatever
  options:  { case_sensitive: false }
  class:    sfRegexExtendedRoute
...
 

Clear your cache:

> php symfony cc
 

Your url matching for that route should now be case-insensitive.

Changing case_sensitive: false to case_sensitive: true in routing.yml will revert the matching for that route back to (symfony default) case-sensitive matching.

by Nickolas Daskalou on 2009-05-24, tagged caseinsensitive  casesensistive  expression  regex  regular  route  routes  routing 

Auto-detect format (from URL and/or Acceptable content types)

As of Symfony 1.1, using alternative formats has become simple. However, automatic use of these formats is now disabled by default.

In the URL

Add support for sf_format in your routing factory :

/apps/myapp/config/factories.yml

all:
  routing:
    ...
    param:
      ...
      suffix: .:sf_format
 

This way, each time you go to http://mysite/module.format, the corresponding format will be detected and used.

Following URLs will automatically work and be rendered as XML : - http://mysite/module.xml # renders indexSuccess.xml.php - http://mysite/module/action.xml # renders actionSuccess.xml.php - http://mysite/module/action/param/value.xml # equals http://mysite/module/action.xml?param=value

If you want the suffix not to be mandatory (e.g. http://mysite/module/action == http://mysite/module/action.html) you'll have to add "sf_format: html" in you routing rules parameters. This is required if you want your old calls to link_to() and url_for() (who don't specifies sf_format parameter) still work.

/apps/myapp/config/routing.yml

default:
  url:   /:module/:action/*
 

myTemplate.php

link_to('hey !', 'module/action') // Produces an error !
link_to('hey !', 'module/action?sf_format=html') // Works, but hey, you'll have to add this to *all* your links :/
 

Add the default format :

/apps/myapp/config/routing.yml

default:
  url:   /:module/:action/*
  param: { sf_format: html }
 

myTemplate.php

link_to('hey !', 'module/action') // Works :)
link_to('hey !', 'module/action?sf_format=xml') // Works too, of course :)
 

Using the Accept HTTP header

We can use a filter, or plug to an even in the ProjectConfiguration class. My choice was a filter.

/apps/myapp/config/filters.yml

# insert your own filters here
detect_format:
  class: DetectFormatFilter
 

/lib/DetectFormatFilter.php

class DetectFormatFilter extends sfFilter
{
 
  public $default_formats = array(
    'html' => array('text/html', 'application/xhtml+xml'),
  );
 
  public function execute($filterChain)
  {
    $request = $this->context->getRequest();
 
    // Only if format is not already defined
    if (!$request->getRequestFormat()) {
 
      // Complete with the filter default formats
      foreach ($this->default_formats as $format => $mimeTypes) {
        foreach ($mimeTypes as $mimeType) {
          if (!$request->getFormat($mimeType)) {
            $request->setFormat($format, $mimeType);
          }
        }
      }
 
      // Detect format depending on acceptable contant types : first is prefered
      foreach ($request->getAcceptableContentTypes() as $mimeType) {
        if ($format = $request->getFormat($mimeType)) {
          $request->setRequestFormat($format);
          break;
        }
      }
 
    }
 
    // execute next filter
    $filterChain->execute();
  }
 
}
 

Take a look at the fact we automatically add "html" to the formats, attached to the two standard mime types. This could be done by adding the corresponding options to the request part of factories.yml, but I wanted this filter to be "ready-to-use" without touching other parts of configuration. "text/html" must be handled apart because as "html" is the default format, Symfony just does not handle it. So when we browse "Accept" headers, we will pass on "text/html", and just skip it because it's not configured. Without adding this part, "html" format would never been rendered, quite annoying isn't it ;)

With this configuration, you can just add the "Accept" header to say you want an XML content, and your ".xml.php" templates will then be rendered.

An interesting evolution of this filter would be to allow it to test if the format can be rendered, and if not, fall back to a default one.

Conclusion

It's quite easy to enable back auto-detection of the format. However, as Fabien says in the ticket 4920, Accept header is sometimes unreliable, and some badly configured browsers could make you think they prefer XML, which will not be the case of the user behind this browser ;)

That said, even if the URL suffix method is probably the best choice, this auto-detection method works and could be useful in some projects...

by Nicolas Chambrier on 2009-03-21, tagged accept  filter  format  routing 
(2 comments)

forward wrapper that accepts a routing label

Here is a forward method wrapper that accepts a routing label. Since forward does not do parameters neither does this. Though it would be nice.

public static function fooForward(&$sfa, $route)
  {
    $url = sfContext::getInstance()->getController()->genUrl($route);
    $routing = explode("/", $url, 3);
    switch (count($routing))
    {
      case (3):
        $sfa->forward($routing[1],$routing[2]);
        break;
      case (2):
        $sfa->forward($routing[1], 'index');
        break;
      default:
        error_log(sprintf("Error in fooForward for routing label %s", $route));
        break;
    }
  }

Call it in an action:

  public function handleError()
  {
    fooTools::fooForward($this, '@someLabel');
  }

If there where parameters in the label the $url would have them. Though they can not be used in this case.

by roland cruse on 2006-09-27, tagged action  forward  handleerror  routing 
(3 comments)

How to have common parameters for every route

Let's take an example. You want to have the user culture in front of each of your URL.

So, your routing configuration look like something like that:

page:
  url: /:culture/:page
  params: ...

article:
  url: /:culture/:year/:month/:day/:slug
  params: ...

...

This is the easy part. But now, every time you call url_for() or link_to() you have to pass the culture parameter.

For such situations, there is a better way. The routing has a special configuration parameter named sf_routing_defaults that you can set with default values:

sfConfig::set('sf_routing_defaults', array('culture' => 'en'));

And now, the culture parameter will automatically added to the parameters that you pass to the url_for() or link_to() helpers.

by Fabien Potencier on 2006-08-23, tagged i18n  routing 
(8 comments)

Get the module, action and parameters from a url

The problem

You want to get the module, action and parameters associated to a given url, pretty much as the routing system does.

The solution

First you will have to remove the part of the url which is not symfony specific. That part typically looks like yoursite.com/path/to/symfony. Once you've done that execute the following code:

$r = new sfRouting();
$r->setRoutes(sfRouting::getInstance()->getRoutes());
$params = $r->parse($myUrl);
$module = $params['module'];
$action = $params['action'];

Now the module and action associated to this url are as above in $module and $action and the parameters are the remaining elements of the array $params.

by Olivier Verdier on 2006-07-21, tagged action  module  parameters  routing  url 
(12 comments)