How to handle sfError404Exception

Janusz Slota - Tue 06 December 2011 - php, symfony1

The problem:

Let's say you need to migrate old website to you new Symfony app. You want to keep all old links working. When they do not match the new link scheme, you want to redirect with 301 (or 302) to correct page in the new URL scheme. All these can be managed via backend module.

My first thought was I need a filter to catch sfError404Exception and handle redirections from there. Well, in Symfony 1.4 you can't do it without changing the Symfony core files, so it's not an option. This is because the filters are not even get called, as routing finds out first, that the module and/or action does not exists and throws this exception.

The solution: There is the easier way of doing this. First you need to add new route that catches all that does not match current link scheme. In fact you need to replace default and default_index routes with something like this:

# apps/frontend/config/routing.yml

your_other_routes_here: ~

# generic rules
# please, remove them by adding more specific rules
# default_index:
#  url:   /:module
#  param: { action: index }

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

# at the very end of the file
catchall:
  url:   /*
  param: { module: default, action: catchall }

If you still need default and default_index routes you need to somehow handle forwards in your executeCatchall action. Please note that the above catchall route cannot be combined together with default and default_index. If you put catchall above these two, they won't be executed ever, on the other hand - if you put it below these two, catchall will never be executed.

Then in this case you need to create executeCatchall action in your default module:

<?php

// apps/frontend/modules/default/actions/actions.class.php

class defaultActions extends sfActions
{
  public function executeCatchall(sfWebRequest $request)
  {
    // find your redirectation using $request->getPathInfo()
    $redirection = Doctrine_Core::getTable('Redirection')
      ->findOneByPathInfo($request->getPathInfo());

    // this ensures that if there is no specified redirection,
    // the old sfError404Exception gets thrown
    $this->forward404Unless($redirection);

    // redirect
    $this->redirect($redirection['redirect_to']);
  }
}

In the above example I'm assuming the following model class:

Redirection:
  columns:
    path_info:        { type: string(255) }
    redirect_to:      { type: string(255) }

Of course this is very basic implementation of redirection functionality, but it shows the correct way of doing it. I hope it'll help.

What's wrong with handling sfError404Exception inside the execute404() action?

Well, it often happens that you need to throw sfError404Exception in your actions. It doesn't feel right for me to throw this exception inside the method that handles it.