Completely custom Symfony error pages

For every Symfony project I work on, I eventually want to get rid of the tasteful brown error pages with the Symfony logo on them and install my own, so the users don’t get weirded out and somehow start thinking “Symfony = error”. Also, I’m probably overly optimistic, but it would be nice if there was a valid email address on the error page so I could hear users complain.

The documentation is good, but I want a simpler recipe that also accounts for all the forseeable errors, even those that don’t get to Symfony. Some details vary by version, but in short, for symfony 1.1:

Test URLs

We want to ultimately make sure that these URLs behave the right way:

Normal errors (must have)– 404 Not Found and 500 Server Error

http://localhost/myapp/default/error404 The “real” 404 page… it’s actually strangely “found” in this case.
http://localhost/myapp/mymodule/view/id/22222 A forward404Unless style forwarding should get the custom 404 page. This is really the most likely case– if a user follows a link to an object another user has deleted, I want them to see the nice page.
http://localhost/myapp/junk.php , or http://localhost/myapp/images/junk.gif A request that Apache handles should display the custom 404 page too.
http://localhost/myapp/errors/error500.php The “real” URL of the 500 error page should work.
http://localhost/myapp/awfijwef/awfijawfo Another real 404 error– Symfony forwards nonsensical module/action stuff to the custom 404 page.

I have seen error 500 pages countless times during app development, but I just could NOT reproduce one to test the 500 error page. Any quick recipes for that, anyone?

More esoteric error conditions– app unavailable/locked, module disabled

http://localhost/myapp/errors/unavailable.php The “unavailable” page for when a whole symfony app is locked with symfony project:disable app env*, or while the cache is being cleared.
any application URL after I run symfony project:disable and I have the check_lock setting on* should go to the ‘unavailable’ page (unavailable.php)
http://localhost/myapp/default/disabled The real “I’m disabled” page for when a module is locked with in the module’s config file, e.g. apps/frontend/modules/mymodule/config/module.yml
http://localhost/myapp/mymodule/myaction , when I’ve explicitly disabled the module by putting this in apps/frontend/mymodule/config/module.yml:
all:
  enabled:     false
The disabled page (above)

Steps

  1. manually create a few files for the default module. DO NOT use symfony generate:module because you only want to override a small bit of the built-in default module (found in /path/to/symfony/lib/controller/default/templates/* incidentally, if you ever want to see what you’re overriding).
    $ mkdir apps/frontend/modules/default
    $ mkdir apps/frontend/modules/default/config
    $ mkdir apps/frontend/modules/default/templates
    $ touch apps/frontend/modules/default/config/view.yml
    $ touch apps/frontend/modules/default/templates/error404Success.php
    $ touch apps/frontend/modules/default/templates/disabledSuccess.php
  2. Edit the templates I just made– remember, the content for these goes inside your sitewide layout.php. I started out by copying the default templates in lib/controller/default/templates/ from my Symfony distribution.
  3. create mostly static pages for web/errors/error500.php and unavailable.php*** They won’t be wrapped in layout.php so they need to have the full page structure, and basically no symfony-involving PHP code (like helpers, too bad), though I do use a bit of really basic stuff, like including the URL in my mailto form on the 500 page:
      <dl class="sfTMessageInfo">
        <dt>Something is terribly broken.</dt>
        <dd>
    	  <p>
    Please 
    <a href="mailto:webmaster@mycompany.com?subject=500 error at http://
    <?php echo $_SERVER['SERVER_NAME'] . '/'. $_SERVER['REQUEST_URI']  ?>
    <?php 
    if (array_key_exists('HTTP_REFERER', $_SERVER)) {
      echo "&body=Referrer: ".$_SERVER['HTTP_REFERER'];
    }
     ?>">
    e-mail us at webmaster@mycompany.com</a> and let us know what you were doing when this error occurred. We will fix it as soon as possible.
    </p>
    	  <p>
       If you have an urgent problem, please call the Support Desk at XXXX.
    </p>
    	  <p>
       You may also find the <a href="/README.html">documentation</a> useful.
    </p>
    </dd>
    </dl>
  4. Include my stylesheets, in view.yml:
    error404Success:
      metas:
        title:        ED Log 404 Not Found
      stylesheets:
        - -*
        - mystyles1: { position: last }
        - mystyles2: {position: last}
        - /sf/sf_default/css/screen.css
        - /sf/sf_default/css/ie.css
        - errors: {position: last}
    
    (and the same thing for disabledSuccess:) My web/css/errors.css overrides some of the default symfony error page styles (compare web/sf/sf_default/css/screen.css in your symfony distribution) to make them less brown, but I keep the general thing.
  5. If your application has security on by default, make sure to un-secure the error pages so people can see them without logging in. Add these above the default rule in apps/frontend/config/security.yml:
    error404:
      is_secure: off
    
    disabled:
      is_secure: off
    
  6. Tell Apache where to find total 500 errors and 404 errors that don’t make it to symfony (like anything that doesn’t get to a front controller, like a misspelled controller or image name):
      <Directory "/var/www/my_sf_app/web">
            ErrorDocument 404 /default/error404
    	ErrorDocument 500 /errors/error500.php
      </Directory>
  7. Hrm, the table of URLs above sounds like a good test script… BUT, it resists scripting because most of the error URLs are outside symfony, so you don’t get the custom error pages when going to an actual error with sfTestBrowser, or even a regular sfBrowser, because instead you get fancy exception stuff. So, here’s a pretty lame test script I put in test/functional/frontend/customErrors.php that tests the easiest cases, but it’s not hard to manually try the others with a browser. I welcome suggestions for improvement!
     
    <?php
     
    include(dirname(__FILE__).'/../../bootstrap/functional.php');
     
    $e404_regexp = "webmaster@mycompany";
    $disabled_regexp = "xXXXX";
     
    // create a new test browser
    $browser = new sfTestBrowser();
     
    /* http://localhost/myapp/default/error404	 
    The "real" 404 page */
     
    $browser->
      get('/default/error404')->
      isStatusCode(404)->
      isRequestParameter('module', 'default')->
      isRequestParameter('action', 'error404')->
      checkResponseElement('body', "/$e404_regexp/");
     
     
    /* http://localhost/myapp/default/disabled	 
    The "unavailable" page for when symfony is locked with symfony project:disable */
     
    $browser->
      get('/default/disabled')->
      isStatusCode(200)->
      isRequestParameter('module', 'default')->
      isRequestParameter('action', 'disabled')->
      checkResponseElement('body', "/$disabled_regexp/");
    And a few passing tests is better than none, right?
    $ php ./symfony test:functional frontend customError
    # get /default/error404
    ok 1 - status code is 404
    ok 2 - request parameter module is default
    ok 3 - request parameter action is error404
    ok 4 - response selector body matches regex /webmaster@mycompany/
    # get /default/disabled
    ok 5 - status code is 200
    ok 6 - request parameter module is default
    ok 7 - request parameter action is disabled
    ok 8 - response selector body matches regex /xXXXX/
    1..8
     Looks like everything went fine.
    

Sheesh, it’s still a little too involved for such a small benefit. If anyone has any ideas on how to streamline this procedure, let me know! Maybe it could be a plugin or an enhancement to symfony?

  • Disabling projects only works if the check_lock option is enabled in e.g. apps/frontend/config/settings.yml

** Symfony 1.0 default templates are found in …/symfony/data/modules/default/templates/ and include an ‘unavailable’ template in addition to ‘disabled’. Symfony 1.2 templates are in the same place as in 1.1.

*** Symfony 1.2 wants its 500 error page in config/error/error.html.php