Le fichier de routing

article_show:
  url:     /article/:id/page/:rank.html
  class:   sfDoctrineRoute
  options: { model: Article, type: object }
  param:   { module: article, action: show }
  requirements:
    id:      \d+
    rank:    \d+
    sf_method: [get]

Lien généré ($article est une instance de Article) :

url_for('article_show', $article)
Résultat attendu : /article/1/page/1.html
Résultat obtenu  : /article/1/page/1.html
url_for('article_show', array('sf_subject' => $article, 'rank' => 2))
Résultat attendu : /article/1/page/2.html
Résultat obtenu  : /article/1/page/1.html

On remarque donc que la règle de routing n'a que faire de notre paramètre rank, il sera toujours alimenté par la méthode getRank() de l'objet Article.

Pour réglé ce problème auquel je n'ai pas trouvé de solution sur la Mailing List, j'ai donc décidé d'étendre la classe sfDoctrineRoute, pour que lorsque l'on fourni un paramètre manuellement celui-ci ne soit pas écrasé, par celui récupéré par la méthode de l'objet.

/**
 * myDoctrineRoute
 */
class myDoctrineRoute extends sfDoctrineRoute
{
  protected function convertObjectToArray($object)
  {
    if (is_array($object))
    {
      if (!isset($object['sf_subject']))
      {
        return $object;
      }

      $parameters = $object;
      $object = $parameters['sf_subject'];
      unset($parameters['sf_subject']);
    }
    else
    {
      $parameters = array();
    }

    if(isset($this->options['erase']) && $this->options['erase'] === false)
    {
      $response = array_merge($parameters, $this->doConvertObjectToArray($object));
    }
    else
    {
      // We check the parameters value and unset them if they are null
      // We define an array of null values and we check if exist one to do the foreach, to unset the null values
      $nullValues = array(null, 'null');
      $findNull   = true;
      $iFindNull  = 0;
      while($iFindNull < count($nullValues) && $findNull === false)
      {
        $findNull = in_array($nullValues[$iFindNull], $parameters);
        $iFindNull++;
      }

      if($findNull)
      {
        foreach($parameters as $parameterKey => $parameterValue)
        {
          if(in_array($parameterValue, $nullValues))
          {
            unset($parameters[$parameterKey]);
          }
        }
      }
      $response = array_merge($this->myDoConvertObjectToArray($object, $parameters), $parameters);
    }

    return $response;
  }

  protected function myDoConvertObjectToArray($object, $defaultParameters = array())
  {
    if (isset($this->options['convert']) || method_exists($object, 'toParams'))
    {
      return parent::doConvertObjectToArray($object);
    }

    $className = $this->options['model'];

    $parameters = array();

    foreach ($this->getRealVariables() as $variable)
    {
      if(!in_array($variable, array_keys($defaultParameters)))
      {
        try {
          $parameters[$variable] = $object->$variable;
        } catch (Exception $e) {
          try {
            $method = 'get'.sfInflector::camelize($variable);
            $parameters[$variable] = $object->$method;
          } catch (Exception $e) {}
        }
      }
    }
    return $parameters;
  }
}



Après cela il faut donc changé notre de règle de routing, avec en bonus un paramètre pour définir le comportement de votre règle, si celle-ci doit écrasé les paramètres renseignés manuellement ou non, à l'aide de l'option erase qui par défaut est à true:

article_show:
  url:     /article/:id/page/:rank.html
  class:   myDoctrineRoute
  options: { model: Story, type: object, erase: true }
  param:   { module: story, action: index }
  requirements:
    id:      \d+
    rank:    \d+
    sf_method: [get]


N'hésitez pas maintenant à me faire part de vos impressions, car je pense qu'il existe déjà une solution et j'ai d'ailleurs trouvé cela étrange de ne pas la trouver.