Archives


Master Symfony2 fundamentals

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).
trainings.sensiolabs.com

Discover the SensioLabs Support

Access to the SensioLabs Competency Center for an exclusive and tailor-made support on Symfony
sensiolabs.com

Fabien Potencier
Cross Application Links
by Fabien Potencier – February 17, 2009 – 15 comments

A symfony project is made of one or more applications. Applications share nothing, but the model classes. But, even if the symfony documentation is crystal clear on the subject, many developers want to share more than just the model. The most requested feature being the ability to create links to a frontend application from a backend one. Think about a CMS backend where you want the user to edit an article and then link to the corresponding article in the frontend.

In symfony 1.0, it was next to impossible (you needed to hardcode the frontend routing rule in the backend). In symfony 1.1, it was possible, but a bit tedious, error prone, and so it was not officially documented.

As of symfony 1.2, there is a very simple way to do it, thanks to a small modification we made to the way configuration handler works. Beside parsing the configuration files and converting the resulting array to a PHP cache, some configuration handler classes can now also "evaluate" the configuration for direct consumption (the autoload, database, and routing configuration handlers sports this new behavior).

For example, if you want to convert a file containing route definitions in the YAML format to their corresponding routes objects, you can do something like the following:

$config = new sfRoutingConfigHandler();
$routes = $config->evaluate(array('/path/to/routing.yml'));

If you execute the above code snippet, the $routes array will be populated with an array of route objects, equivalent to the route definitions in the YAML file. The generate() method takes an array of YAML filenames as its first argument and merge the content of the files.

And thanks to the framework decoupling and the sfPatternRouting::setRoutes() method, you can easily create a frontend routing object from any PHP script:

$routing = new sfPatternRouting(new sfEventDispatcher());
$routing->setRoutes($routes);

Generating URLs is now as simple as calling the routing object generate() method:

$routing->generate('homepage');
$routing->generate('article', array('id' => $id));

The generate() method takes a route name as its first argument, and an array of parameters as its second one.

Let's use this knowledge to simplify the creation of frontend URLs from a backend application.

In the backendConfiguration class, add the following code:

// apps/backend/config/backendConfiguration.class.php
class backendConfiguration extends sfApplicationConfiguration
{
  protected $frontendRouting = null;
 
  public function generateFrontendUrl($name, $parameters = array())
  {
    return 'http://frontend.example.com'.$this->getFrontendRouting()->generate($name, $parameters);
  }
 
  public function getFrontendRouting()
  {
    if (!$this->frontendRouting)
    {
      $this->frontendRouting = new sfPatternRouting(new sfEventDispatcher());
 
      $config = new sfRoutingConfigHandler();
      $routes = $config->evaluate(array(sfConfig::get('sf_apps_dir').'/frontend/config/routing.yml'));
 
      $this->frontendRouting->setRoutes($routes);
    }
 
    return $this->frontendRouting;
  }
 
  // ...
}

Notice that the generateFrontendUrl() method always generates absolute URLs for obvious reasons. This is the only information that still need to be hardcoded, as symfony does not have this knowledge and cannot guess it.

With this code in place, you can now generate a frontend URL from anywhere in the backend. Here is for instance how to redirect the user to the frontend from a backend action:

$this->redirect($this->getContext()->getConfiguration()->generateFrontendUrl('hello', array('name' => 'Bar')));

You can also create a small helper for templates:

function link_to_frontend($name, $parameters)
{
  return sfProjectConfiguration::getActive()->generateFrontendUrl($name, $parameters);
}

That's all there is to it!

If you have many applications, it is pretty easy to refactor the above code to generate URL for and from any other application.

The technique described in this post is used internally by symfony for the app:routes tasks.

Comments RSS

  • gravatar
    #1 Tim said on the 2009/02/17 at 09:56
    Great! Exactly what I'm looking for : )
  • gravatar
    #2 Olivier Mansour said on the 2009/02/17 at 10:52
    sounds like this plugin : http://www.symfony-project.org/plugins/omCrossAppUrlPlugin

    in a different way cause it use a context object which cost, I guess, a bit more.
  • gravatar
    #3 Ross said on the 2009/02/17 at 12:30
    This won't work for any routes connected on the application's routing.load_configuration event. For that, I presume that loading the sfContext from the other application is still the way to go.
  • gravatar
    #4 Fabien said on the 2009/02/17 at 14:31
    @Ross: I suppose you are talking about plugins here. As of symfony 1.2, a plugin can contain a routing.yml file. If you do so, the technique I have described here still works.
  • gravatar
    #5 Scott said on the 2009/02/17 at 15:01
    Symfony keeps getting better and better thanks to continual updates and improvements like this one. Thank you.
  • gravatar
    #6 Ariel Arjona said on the 2009/02/17 at 15:15
    excellent!
  • gravatar
    #7 Jeremy said on the 2009/02/17 at 15:20
    It's like you were reading my mind with this one. I was just looking into doing this yesterday. Thanks!
  • gravatar
    #8 David said on the 2009/02/17 at 16:27
    Any idea why I would get this error when I try to use the helper?

    Fatal error: Class 'sfConfiguration' not found in /path/to/apps/backend/lib/helper/BackendHelper.php on line 5
  • gravatar
    #9 Thomas R said on the 2009/02/17 at 17:08
    @david there is a type error in the post, it is sfProjectConfiguration and not sfConfiguration.
  • gravatar
    #10 David said on the 2009/02/17 at 19:34
    @Thomas: Thanks, works great now!
  • gravatar
    #11 jerome said on the 2009/02/18 at 14:59
    test
  • gravatar
    #12 AvidSymfonyUser said on the 2009/02/18 at 19:25
    So could app:routes be used to do the same thing?
  • gravatar
    #13 Dreur said on the 2009/02/21 at 19:11
    private $appRouting = array();

    private function getAppRouting($app)
    {
    if (!key_exists($app, $appRouting))
    {
    $this->$appRouting[$app] = new sfPatternRouting(new sfEventDispatcher());

    $config = new sfRoutingConfigHandler();
    $routes = $config->evaluate(array(sfConfig::get('sf_apps_dir').'/'.$app.'/config/routing.yml'));

    $this->$appRouting[$app]->setRoutes($routes);
    }

    return $this->$appRouting[$app];
    }
  • gravatar
    #14 Dreur said on the 2009/02/21 at 21:08
    What about no_script_name off app does it work with them too ?
    In my project it doesn't seem to. do we have to add the script name in the url ?
  • gravatar
    #15 mppfiles said on the 2009/03/10 at 15:16
    Great, long-waited feature.
    Maybe you can integrate on sfProjectConfiguration the necessary code to get the routes for all applications by default.