Pretty Blue Screen

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
}


Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • ajking

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

  • irkengir

    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.

  • http://www.phppatterns.com HarryF

    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.

  • Justin the Semi-Anonymous Coward

    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.

  • http://www.sudokumadness.com/ coffee_ninja

    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 ;)

  • http://www.phpism.net Maarten Manders

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

  • http://www.phppatterns.com HarryF

    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.

  • irkengir

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

  • Pingback: Scriptorama » Blog Archive » Exceptions en stacktraces

  • shea

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

    Haha! Good call.

  • Peter Nagy

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

  • http://www.phpdeveloper.org enygmadae

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

  • Buddha443556

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

    I second that request. A download would be nice.

  • Buddha443556

    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.

  • http://www.phppatterns.com HarryF

    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?

  • Wilson Miner

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

  • http://www.sudokumadness.com/ coffee_ninja

    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 :)

  • http://www.phppatterns.com HarryF

    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).

  • Marco

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

  • Arnaud

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

  • ahmed saad

    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?

  • antiHERO

    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);

  • melitonas

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

  • Gavin

    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 :)

  • pfitz

    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.

  • Jorrit Schippers

    The images are down

  • Anonymous

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