Pretty Blue Screen

By | | Programming

Been playing around with Zend’s framework some more and finally got annoyed enough about how exceptions are displayed to do something about it.

The code at the bottom of this post just needs including somewhere and enables a “pretty” exception handler – it’s not specific to Zend’s framework – to use it just…


<?php
// Require somewhere like index.php
require_once 'PrettyBlueScreen.php';

…and away you go.

The idea is not original – it’s PHP port from webpy which used code from Django – imitation and flattery.

What it does, should an uncaught exception occur;

Pretty Colours

Blue Screen

Stack Trace

Stack Trace

Call Arguments

Call Arguments

Source Code Snippets

Source Code

Request Information

Request

Response Headers

Response

At the moment it’s only for PHP 5 exceptions, although it shouldn’t be too hard to modify it to handle normal PHP errors. It probably only works with PHP 5.1+ (have only tested on 5.1.2) but some more tweaking could fix that (at least for PHP 5.0).

Important note: this is only for development – do not place on a public web server – the only information it lacks is your PIN number…

Anyway – the code;


<?php
set_exception_handler('PrettyBlueScreen');
function PrettyBlueScreen($e) {
  $o = create_function('$in','echo htmlspecialchars($in);');
  $sub = create_function('$f','$loc="";if(isset($f["class"])){
    $loc.=$f["class"].$f["type"];}
    if(isset($f["function"])){$loc.=$f["function"];}
    if(!empty($loc)){$loc=htmlspecialchars($loc);
    $loc="<strong>$loc</strong>";}return $loc;');
  $parms = create_function('$f','$params=array();if(isset($f["function"])){
    try{if(isset($f["class"])){
    $r=new ReflectionMethod($f["class"]."::".$f["function"]);}
    else{$r=new ReflectionFunction($f["function"]);}
    return $r->getParameters();}catch(Exception $e){}}
    return $params;');
  $src2lines = create_function('$file','$src=nl2br(highlight_file($file,TRUE));
    return explode("<br />",$src);');
  $clean = create_function('$line','return trim(strip_tags($line));');
  $desc = get_class($e)." making ".$_SERVER['REQUEST_METHOD']." request to ".
    $_SERVER['REQUEST_URI'];
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta name="robots" content="NONE,NOARCHIVE" />
  <title><?php $o($desc);?></title>
  <style type="text/css">
    html * { padding:0; margin:0; }
    body * { padding:10px 20px; }
    body * * { padding:0; }
    body { font:small sans-serif; background: #70DBFF; }
    body>div { border-bottom:1px solid #ddd; }
    h1 { font-weight:normal; }
    h2 { margin-bottom:.8em; }
    h2 span { font-size:80%; color:#666; font-weight:normal; }
    h2 a { text-decoration:none; }
    h3 { margin:1em 0 .5em 0; }
    h4 { margin:0.5em 0 .5em 0; font-weight: normal; font-style: italic; }
    table {
        border:1px solid #ccc; border-collapse: collapse; background:white; }
    tbody td, tbody th { vertical-align:top; padding:2px 3px; }
    thead th {
        padding:1px 6px 1px 3px; background:#70FF94; text-align:left;
        font-weight:bold; font-size:11px; border:1px solid #ddd; }
    tbody th { text-align:right; color:#666; padding-right:.5em; }
    table.vars { margin:5px 0 2px 40px; }
    table.vars td, table.req td { font-family:monospace; }
    table td { background: #70FFDB; }
    table td.code { width:95%;}
    table td.code div { overflow:hidden; }
    table.source th { color:#666; }
    table.source td {
        font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
    ul.traceback { list-style-type:none; }
    ul.traceback li.frame { margin-bottom:1em; }
    div.context { margin:5px 0 2px 40px; background-color:#70FFDB; }
    div.context ol {
        padding-left:30px; margin:0 10px; list-style-position: inside; }
    div.context ol li {
        font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
    div.context li.current-line { color:black; background-color:#70FF94; }
    div.commands { margin-left: 40px; }
    div.commands a { color:black; text-decoration:none; }
    p.headers { background: #70FFDB; font-family:monospace; }
    #summary { background: #00B8F5; }
    #summary h2 { font-weight: normal; color: #666; }
    #traceback { background:#eee; }
    #request { background:#f6f6f6; }
    #response { background:#eee; }
    #summary table { border:none; background:#00B8F5; }
    #summary td  { background:#00B8F5; }
    .switch { text-decoration: none; }
    .whitemsg { background:white; color:black;}
  </style>
  <script type="text/javascript">
  //<!--
    function getElementsByClassName(oElm, strTagName, strClassName){
        // Written by Jonathan Snook, http://www.snook.ca/jon;
        // Add-ons by Robert Nyman, http://www.robertnyman.com
        var arrElements = (strTagName == "*" && document.all)? document.all :
        oElm.getElementsByTagName(strTagName);
        var arrReturnElements = new Array();
        strClassName = strClassName.replace(/\-/g, "\\-");
        var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
        var oElement;
        for(var i=0; i<arrElements.length; i++){
            oElement = arrElements[i];
            if(oRegExp.test(oElement.className)){
                arrReturnElements.push(oElement);
            }
        }
        return (arrReturnElements)
    }
    function hideAll(elems) {
      for (var e = 0; e < elems.length; e++) {
        elems[e].style.display = 'none';
      }
    }
    function toggle() {
      for (var i = 0; i < arguments.length; i++) {
        var e = document.getElementById(arguments[i]);
        if (e) {
          e.style.display = e.style.display == 'none' ? 'block' : 'none';
        }
      }
      return false;
    }
    function varToggle(link, id, prefix) {
      toggle(prefix + id);
      var s = link.getElementsByTagName('span')[0];
      var uarr = String.fromCharCode(0x25b6);
      var darr = String.fromCharCode(0x25bc);
      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
      return false;
    }
    function sectionToggle(span, section) {
      toggle(section);
      var span = document.getElementById(span);
      var uarr = String.fromCharCode(0x25b6);
      var darr = String.fromCharCode(0x25bc);
      span.innerHTML = span.innerHTML == uarr ? darr : uarr;
      return false;
    }
    window.onload = function() {
      hideAll(getElementsByClassName(document, 'table', 'vars'));
      hideAll(getElementsByClassName(document, 'div', 'context'));
      hideAll(getElementsByClassName(document, 'ul', 'traceback'));
      hideAll(getElementsByClassName(document, 'div', 'section'));
    }
    //-->
  </script>
</head>
<body>
<div id="summary">
  <h1><?php $o($desc);?></h1>
  <h2><?php
    if ( $e->getCode() ) { $o($e->getCode()). ' : '; }
    ?> <?php $o($e->getMessage()); ?></h2>
  <table>
    <tr>
      <th>PHP</th>
      <td><?php $o($e->getFile()); ?>, line <?php $o($e->getLine()); ?></td>
    </tr>
    <tr>
      <th>URI</th>
      <td><?php $o($_SERVER['REQUEST_METHOD'].' '.
        $_SERVER['REQUEST_URI']);?></td>
    </tr>
  </table>
</div>
<div id="traceback">
  <h2>Stacktrace
    <a href='#' onclick="return sectionToggle('tb_switch','tb_list')">
    <span id="tb_switch">&#x25b6;</span></a></h2>
  <ul id="tb_list" class="traceback">
    <?php $frames = $e->getTrace(); foreach ( $frames as $frame_id => $frame ) { ?>
      <li class="frame">
        <?php echo $sub($frame); ?>
        [<?php $o($frame['file']); ?>, line <?php $o($frame['line']);?>]
        <?php
        if ( count($frame['args']) > 0 ) {
          $params = $parms($frame);
        ?>
          <div class="commands">
              <a href='#' onclick="return varToggle(this, '<?php
              $o($frame_id); ?>','v')"><span>&#x25b6;</span> Args</a>
          </div>
          <table class="vars" id="v<?php $o($frame_id); ?>">
            <thead>
              <tr>
                <th>Arg</th>
                <th>Name</th>
                <th>Value</th>
              </tr>
            </thead>
            <tbody>
                <?php
                foreach ( $frame['args'] as $k => $v ) {
                  $name = isset($params[$k]) ? '$'.$params[$k]->name : '?';
                ?>
                <tr>
                  <td><?php $o($k); ?></td>
                  <td><?php $o($name);?></td>
                  <td class="code">
                    <div><?php highlight_string(var_export($v,TRUE));?></div>
                  </td>
                </tr>
                <?php
                }
                ?>
            </tbody>
          </table>
        <?php } if ( is_readable($frame['file']) ) { ?>
        <div class="commands">
            <a href='#' onclick="return varToggle(this, '<?php
            $o($frame_id); ?>','c')"><span>&#x25b6;</span> Src</a>
        </div>
        <div class="context" id="c<?php $o($frame_id); ?>">
          <?php
          $lines = $src2lines($frame['file']);
          $start = $frame['line'] < 5 ?
            0 : $frame['line'] -5; $end = $start + 10;
          $out = '';
          foreach ( $lines as $k => $line ) {
            if ( $k > $end ) { break; }
            $line = trim(strip_tags($line));
            if ( $k < $start && isset($frames[$frame_id+1]["function"])
              && preg_match(
                '/function(&nbsp;)*'.preg_quote($frames[$frame_id+1]["function"]).'/',
                  $line) ) {
              $start = $k;
            }
            if ( $k >= $start ) {
              if ( $k != $frame['line'] ) {
                $out .= '<li><code>'.$clean($line).'</code></li>'."\n"; }
              else {
                $out .= '<li class="current-line"><code>'.
                  $clean($line).'</code></li>'."\n"; }
            }
          }
          echo "<ol start=\"$start\">\n".$out. "</ol>\n";
          ?>
        </div>
        <?php } else { ?>
        <div class="commands">No src available</div>
        <?php } ?>
      </li>
    <?php } ?>
  </ul>
</div>
<div id="request">
  <h2>Request
    <a href='#' onclick="return sectionToggle('req_switch','req_list')">
    <span id="req_switch">&#x25b6;</span></a></h2>
  <div id="req_list" class="section">
    <?php
    if ( function_exists('apache_request_headers') ) {
    ?>
    <h3>Request <span>(raw)</span></h3>
    <?php
      $req_headers = apache_request_headers();
        ?>
      <h4>HEADERS</h4>
      <?php
      if ( count($req_headers) > 0 ) {
      ?>
        <p class="headers">
        <?php
        foreach ( $req_headers as $req_h_name => $req_h_val ) {
          $o($req_h_name.': '.$req_h_val);
          echo '<br>';
        }
        ?>
        </p>
      <?php } else { ?>
        <p>No headers.</p>
      <?php } ?>
      <?php
      $req_body = file_get_contents('php://input');
      if ( strlen( $req_body ) > 0 ) {
      ?>
      <h4>Body</h4>
      <p class="req" style="padding-bottom: 2em"><code>
        <?php $o($req_body); ?>
      </code></p>
      <?php } ?>
    <?php } ?>
    <h3>Request <span>(parsed)</span></h3>
    <?php
    $superglobals = array('$_GET','$_POST','$_COOKIE','$_SERVER','$_ENV');
    foreach ( $superglobals as $sglobal ) {
      $sfn = create_function('','return '.$sglobal.';');
    ?>
    <h4><?php echo $sglobal; ?></h4>
      <?php
      if ( count($sfn()) > 0 ) {
      ?>
      <table class="req">
        <thead>
          <tr>
            <th>Variable</th>
            <th>Value</th>
          </tr>
        </thead>
        <tbody>
          <?php
          foreach ( $sfn() as $k => $v ) {
          ?>
            <tr>
              <td><?php $o($k); ?></td>
              <td class="code">
                <div><?php $o(var_export($v,TRUE)); ?></div>
                </td>
            </tr>
          <?php } ?>
        </tbody>
      </table>
      <?php } else { ?>
      <p class="whitemsg">No data</p>
      <?php } } ?>
  </div>
</div>
<?php if ( function_exists('headers_list') ) { ?>
<div id="response">
  <h2>Response
    <a href='#' onclick="return sectionToggle('resp_switch','resp_list')">
    <span id="resp_switch">&#x25b6;</span></a></h2>
  <div id="resp_list" class="section">
    <h3>Headers</h3>
    <?php
    $resp_headers = headers_list();
    if ( count($resp_headers) > 0 ) {
    ?>
    <p class="headers">
      <?php
      foreach ( $resp_headers as $resp_h ) {
        $o($resp_h);
        echo '<br>';
      }
      ?>
    </p>
    <?php } else { ?>
      <p>No headers.</p>
    <?php } ?>
</div>
<?php } ?>
</body>
</html>
<?php
}

Learn Responsive Web Design

Join Learnable $29 Includes all SitePoint books

{ 26 comments }

Anonymous July 13, 2011 at 6:08 pm

Doesn’t handle closures overly well due to obviously missing file and line indices, and those colors! woof! – But very handy – ideal for development

Jorrit Schippers May 13, 2008 at 7:18 pm

The images are down

pfitz October 25, 2007 at 10:11 am

Thnk harry, this is coming in quote handy. As for the Zend Framework, I’m using a lot of it *not the MVC parts just yet*, and its goign very well.

Gavin October 11, 2006 at 7:54 am

Very nice. This pretty blue screen’s popularity leads me to ask, would you like to contribute it to the Zend Framework? We have an easy contribution process :)

melitonas September 20, 2006 at 7:53 pm

Will this Custom error handler be called regardless of the silencing operator “@” ?????

antiHERO May 28, 2006 at 9:51 pm

well, i basically did the same thing on my one, but i implemented the highlight_file function, so that you have syntax highlighting in your code snippet

btw. you should change line 208 from:
$end = $start + 10;
to something like
$end = count($lines) > $start+10 ? $start+10 : count($lines);

ahmed saad April 25, 2006 at 11:21 am

There lies the hardest part of implementing that—”sending” to the client.

what about a Symfony-style little “debug” box on the upper right corner?

Arnaud April 21, 2006 at 8:39 pm

Marco, you should have a look at the ezComponent translation component. It is pretty easy to integrate with the zend framework.

Marco April 6, 2006 at 11:23 am

Hope someone will contribute some i18n stuff to Zend framework, it’s really a mandatory feature for me

HarryF April 6, 2006 at 3:12 am

Oops, just found the listing. The javascript that does the numbering is a little slow on my system. Slow enough for FF to ask to continue … which I kept aborting.

Have tried re-producing this – on my system only get the same effect if I have a call stack of 500+ function calls (used a recursive function to simulate).

It’s possible to fix this by moving the “hideAll” functionality from window.onload into the CSS itself, at the cost of requiring clients to have Javascript enabled (perhaps not a problem given this is meant for developers).

coffee_ninja April 5, 2006 at 12:58 pm

Me: And if you wanted to get really fancy you could catch the exception in PHP and convert it to a Javascript exception object using a JSON library, to be sent to the client…

HarryF: There lies the hardest part of implementing that—”sending” to the client.

Very true Harry, very true. Damn that stateless protocol! Of course since exceptions are thrown as output is being generated, one might find a method of generating output of the JavaScript variety that redirects the Exceptions to a separate view. Naturally I’m saying this as someone who has absolutely no idea how to implement the idea :)

Wilson Miner April 5, 2006 at 12:13 pm

Man, those styles look familiar! Soon, all web frameworks will include Django’s pretty error pages!

HarryF April 5, 2006 at 10:21 am

Could you make the file available for download without the line numbers?

Just click on the “View Plain” link, right at the start of the listing

Oops, just found the listing. The javascript that does the numbering is a little slow on my system. Slow enough for FF to ask to continue … which I kept aborting.

The numbering is actually just the ol tag which gets a start attribute. Could be something else happening, like the “hide all” – how many elements in the stack trace? Also any functions with large numbers of arguments?

Buddha443556 April 5, 2006 at 10:10 am

Oops, just found the listing. The javascript that does the numbering is a little slow on my system. Slow enough for FF to ask to continue … which I kept aborting.

Buddha443556 April 5, 2006 at 9:58 am

Could you make the file available for download without the line numbers?

I second that request. A download would be nice.

enygmadae April 5, 2006 at 8:54 am

Could you make the file available for download without the line numbers?

Peter Nagy April 5, 2006 at 7:27 am

Nice code!
As I see, the xdebug extension did the same thing, but on a lower level.

shea April 5, 2006 at 6:16 am

The colours have been scientifically selected to induce a level of unease, appropriate with having failed to catch an exception.

Haha! Good call.

irkengir April 5, 2006 at 4:06 am

Thanks for your advice Harry and Justin. You’ve made it solid in my mind now; the Zend Framework is for me.

HarryF April 5, 2006 at 3:37 am

And if you wanted to get really fancy you could catch the exception in PHP and convert it to a Javascript exception object using a JSON library, to be sent to the client.

There lies the hardest part of implementing that – “sending” to the client.

But why, why the phpPatterns eye cancer colors? ;)

The colours have been scientifically selected to induce a level of unease, appropriate with having failed to catch an exception.

Maarten Manders April 5, 2006 at 2:08 am

Nice! Saved me from doing that myself one day. But why, why the phpPatterns eye cancer colors? ;)

coffee_ninja April 4, 2006 at 11:42 pm

Getting back to this “pretty exception handler”…

What would be really fancy is if this was hacked so that the exceptions are displayed in an entirely separate “debug” window, instead of inline. And if you wanted to get really fancy you could catch the exception in PHP and convert it to a Javascript exception object using a JSON library, to be sent to the client. I’m not sure at the moment why you would want to do this, but I’m sure you Web 2.0 people will find a reason ;)

Justin the Semi-Anonymous Coward April 4, 2006 at 10:45 pm

I have bene playing with the framework for a short time now and I highly recommend it. It’s still young and a work in progress, but I find it already seems very well built. I like the design thus far and I am glad that it isn’t, as Harry suggested, overly abstracted. I was personally griping a half a year ago about how PHP lacks a solid framework similar to Ruby on rails which favours “convention over configuration”. I like that it makes some assumptions to get started and sets some general good rules to follow in designing your application.

Well Harry is correct that it takes a bit of effort to get your first Hello World (running through a nice URL controller and a View object), it’s worth noting that once you are at that point, it’s _very_ little extra work to take that to the next level and have Hello World become a form which interacts with your database and so on.

Once you learn how the Zend_Controller component works (which I feel is one of the most useful parts of the whole thing because it’s the foundation of the MVC separation), you will be rapidly developing applications in short order.

HarryF April 4, 2006 at 7:15 pm

Harry what is your opinion on the zend framework, is it mature enough to start learning you think? You obvious have quibbles with it.

In general I like it – they’ve avoided abstracting to the nth degree and in some cases, this is cause for debate but the impression I get is it’s a “get stuff done” design – will know once I’ve done something serious with it.

I’ve never used a framework before but i figure if i’m going to learn one it should be by zend.

Think it’s a good choice. Right now you need to be aware that not everything “just works” and you’ll need to put in a little effort to get to “Hello World”. That said, while the code base is still small, it’s probably easier to take it in.

irkengir April 4, 2006 at 7:03 pm

Harry what is your opinion on the zend framework, is it mature enough to start learning you think? You obvious have quibbles with it.

I’ve never used a framework before but i figure if i’m going to learn one it should be by zend.

ajking April 4, 2006 at 6:33 pm

I like it. Nice code. U rock, Harry.

Comments on this entry are closed.

{ 1 trackback }