A Small yet Versatile Debug API for PHP Programs

Introduction

    "Debugging software is not exactly a fun job for developers. The most widely used debugger for PHP still seems to be a var_dump() statement, possibly in conjunction with die() to halt program execution at a certain point.
    "While there is nothing wrong using var_dump() statements in itself, you still need to change the program code to debug a PHP script. And worse, after you have finished debugging, you must remove all var_dump() statements again (well you should, at least). It may well be that a few days later you'll find yourself adding the very same var_dump() statements to your code again because you need to go hunting another bug.
    "Of course, you could just comment out the var_dump() statements, but that looks really ugly in the code. Another option would be to wrap the var_dump() in conditional clauses, and only execute them when, say, a constant DEBUG is defined. This affects performance, because even if the var_dump() statements are not executed, the conditional clause must be executed. And besides, it looks even uglier in the code."
    - Zend Developer Zone

A Modest Proposal

The archive of this code and text: debug.zip. The code is offered to the public domain, and changing it, adding to it—adapting it— is highly encouraged.

This presents some debug code as a way to have all the benefits of var_dump() statements as Zend Developer Zone says, but without any of the drawbacks they outline. This debug code creates no global data (with one optional exception explained below). It is an API and not a Class.

Instead of var_dump() you use debug() which works similarly, but with a few major exceptions. The displayed data:

In addition, the displayed data includes the file and function name in which the debug() call is used. The result is a list of diagnostic data displayed unobtrusively and under control providing a complete runtime view of the code being debugged.

New: This code can debug itself (see Technical Notes).

Update: This release includes a version that is one quarter the original size.

The Setup

The basic operation is to include the file debug.php and strategically place debug() calls throughout your code. If you do just that, nothing will happen and any performance hit will be very small.

Note: I use the term "message" to refer to what debug() logs, but the output depends on it's type (see The Output).

To enable debug output, call the companion function debuglog() with a value to indicate how the debug output will be displayed:

1
Will cause the messages to be displayed in a list at program termination. These output messages are one per line within a <PRE> block with black text and a white background to be independent of the application's HTML (this is configurable).
-1
Will cause the messages to be displayed immediately.
100
Will cause the messages to be displayed immediately and within HTML comments.
200
Will cause the messages to be placed in $GLOBALS['debug_messages'], a string, with each message appended with a <BR>. This is so the data can be incorporated into an application's own HTML output.

Here is an example for the latter:

    <div id="debug">
    <?php print $GLOBALS['debug_messages']; ?>
    </div>

With some appropriate CSS the text can displayed however one sees fit (see the enclosed CSS file for examples).

Note: For an online web application one should only enable debug output if logged in as an Administrator (or via some other mechanism) so that no other visitor sees the output.

New: Just added is a second setup argument to display (dump) global data upon program termination after any debug messages:

1
Display user added data (explained below).
2
Display $_GET and $_POST arrays.
3
Display user defined constants.
4
Display $GLOBALS.

To set both options: debuglog(1,4); to set just the display of data: debuglog('',4).

To add user data to be displayed use a call such as debuglog('dump','data')—the data must be global for only the name of the data is stored. Another example is debuglog('dump','_GET,_POST').

This display is independent from the message log value except that if the log value is 100 this output will be wrapped by HTML comments as well.

The Function

The function debug() has two optional arguments. With no arguments the current debug log status is returned; initially 0 or the value set by debuglog().

The first argument is the variable to be displayed. This is usually a string or an array, but booleans, resources and objects are handled in a meaningful way. Arrays are imploded into a string with associative arrays handled correctly. (See The Output.)

The second argument is overloaded. If -1 the message is displayed immediately. If a positive number the message is truncated to it (to avoid possibly overly long strings). If 'type' the first argument is converted by gettype().

Additional information is displayed for each message—the file, function and line number of the debug() statement, and optionally, the file and line number of the function that called the function that has the debug() statement.

The Display

Here is an example of several messages at the end of a program:

    message log:
    (index.php,37)(error.php,51,modules_init) _config_display
    (display.php,742)(data.php,12,load_php_file) 'translate.ini'
    (html.php,47)(data.php,12,load_php_file) 'htm/templates.php'
    (mysql.php,130)(mysql.php,349,_mysql_query) SELECT id FROM `root` ORDER BY id
    (mysql.php,130)(mysql.php,353,_mysql_query) Object: mysqli_result
    (mysql.php,130)(mysql.php,368,_mysql_query) Array: {1,2,3,4,5,6}

All messages will be converted by htmlentities() so that any HTML in a message will be displayed properly.

The Options

There is a companion function to control debug options during run-time, _debug(), and it takes two arguments: an option name and an option value. The major options and their default values are:

    open      "\n<div style='background:#fff;color:#000;'><pre>"
    close     "</pre></div>"
    start     "<b>message log:</b>"
    caller    1
    newline   1
    trunc     0
    entities  1
    file      NULL
    nofile    NULL

The open and close strings get displayed before and after the debug output, start precedes the debug message list. The caller option adds the caller file and line number preceding the message. The newline option converts newlines in the data to \n. The trunc option will cause all messages to be truncated to it's value if greater than zero. If entities is set to zero the output will not be converted by htmlentities().

The file and nofile options can be used to limit message output. The file option limits messages to messages only from that file. For example, with the call _debug('file','mysql.php'), the output example would be:

    message log:
    (mysql.php,130)(mysql.php,349,_mysql_query) SELECT id FROM `root` ORDER BY id
    (mysql.php,130)(mysql.php,353,_mysql_query) Object: mysqli_result
    (mysql.php,130)(mysql.php,368,_mysql_query) Array: {1,2,3,4,5,6}

And with the call _debug('nofile','data.php'), the output example would be:

    message log:
    (index.php,37)(error.php,51,modules_init) _config_display
    (mysql.php,130)(mysql.php,349,_mysql_query) SELECT id FROM `root` ORDER BY id
    (mysql.php,130)(mysql.php,353,_mysql_query) Object: mysqli_result
    (mysql.php,130)(mysql.php,368,_mysql_query) Array: {1,2,3,4,5,6}

If _debug() is called with a name and without a value, e.g. _debug('newline'), the option's value is returned, empty string if not defined.

The Defines

In addition to controlling debug by setting options as described above, all options can be set by defines before including the debug file:

    DEBUG_LOG
    DEBUG_DUMP
    DEBUG_OPEN
    DEBUG_CLOSE
    DEBUG_START
    DEBUG_EMPTY
    DEBUG_CALLER
    DEBUG_NEWLINE
    DEBUG_ENTITIES

DEBUG_LOG and DEBUG_DUMP are used to set the log/dump level before including the code (and the same as calling debuglog(DEBUG_LOG) and debuglog('',DEBUG_DUMP) respectively).

The Summary

Here is a typical, although rather simplistic, use:

<?php
    
// file test.php

    
include "debug.php";
    
debuglog(1);

    function 
funca($arg) {
        
debug("'$arg'");
        echo 
"function A $arg<br>";
        
funcb();
    }

    function 
funcb() {
        
debug("");
        echo 
"function B<br>";
    }

    
funca("this is a test");
?>

And the output is:

    function A this is a test
    function B
    message log:
    (test.php,19)(test.php,9,funca) 'this is a test'
    (test.php,11)(test.php,15,funcb)

Enclosing a variable in both single and double quotes is to see when a variable is empty. Passing an empty string results in just a "breadcrumb" which is useful to follow code flow.

New: There is an option, DEBUG_EMPTY_STR, that, if true, will display empty strings as "" (see next section). It is optional because that will (kind of) interfere with the concept of breadcrumbs (see end note).

In the above example, turning off debugging is simply commenting out the call to debuglog() or passing it a 0 if your code were to use a configuration setting of some kind. Or the define DEBUG_LOG could be used.

The Output

The following list is how debug() displays it's argument ($arg).

    Type          Output
 
    string        "$arg"
    TRUE          "(true)"
    FALSE         "(false)"
    array         "Array: ".implode($arg)
    object        "Object: ".get_class($arg)
    resource      "Resource: ".get_resource_type($arg)
    empty string  "''" (dependent on DEBUG_EMPTY_STR define)

Other Functions

The code includes a few non-documented functions: Simple error "support" to perhaps be expanded; a way to retrieve function caller data; a var_dump() variant with greater output control; and a simpler version of debug_backtrace().

Caveat

If your code has redirects by header("Location: $URL"), or has direct downloads by header("Content-Type: text/plain"), any debug output will interfere. That can be mitigated by preceding such statements with:

        debuglog(0);

to turn the output off.

WordPress

I have tested this code with WordPress and it works—sort of. Just include the debug.php file in index.php and set the debug level and then place your debug() calls wherever. Of course, that way means every visitor will see the debug output.

WordPress says "Use capability checks..." to check for admin rights, but they do not explain what that is, so I cannot provide an example of how to test for being logged in as Admin (it is not is_admin()) and I don't feel like tracking it down.

You can also add an option, say debug, and use this:

    debuglog(get_option('debug'));

But WordPress is so complicated that I cannot find where in the load process that get_option() becomes available for use; i.e. many functions you may want to debug are called before get_option() is available.

So I give up there.

Also, WordPress has more than one entry point, so including just in index.php may cause Fatal error: Call to undefined function debug.

Technical Notes

The overhead of debug() with debug output off is that of two function calls, six compare operations and one isset().

Something odd needs to be noted. The configuration function, _debug(), stores internal data in a static array which can be read/written by function overloading, which will "self initialize" upon first call. The odd thing is that _debug() can call debuglog() which will then call _debug() back; this is safe because _debug() immediately sets the static variable which it uses to self initialize.

The function debuglog() highlights how _debug() can create and store configuration data at will (i.e. the values reg and dreg because PHP will call a registered function each time it is registered).

To debug this code, debug() can be called anywhere and will work as expected, except within itself (and a simple test can prevent such an infinite loop; see _caller()). To debug inside debug() use _debug('msg',"string").

By installing a PHP error handler (one is included) it can be seen that using the error operator may suppress the displaying of a message but still cause PHP to execute it's error handling code, and that can affect performance. (Sometimes though it is quite appropriate to suppress known possible E_NOTICE messages.) The error handler included logs error messages (and optionally displays them).

Other features are being considered: debug_dump() might use bit fields to decide what data to display; debug() might support more arguments similar to error() (like debug('','-') for a breadcrumb perhaps). However, adding features may be nice but at some point I shall stop doing so—this code should be three things: small, small and small. (At now over 400 lines I consider the code already to be too large. I mark a few sections of the code that can be cut out to reduce the code size to about 200 lines.)

This is also just an idea, and this API can be implemented in any language and applied to any program (I am certainly not the first to think of this). I have, in fact, started a Bash version (the GNU Bourne Again SHell).

Final Note: If anyone has any suggestions or ideas, or has added something, posting a comment about it at Freecode and/or SourceForge (see below) would be a really good thing. (As well as, of course, bug reports.)


Written by Greg Jennings; last update February 2014.

Comment on this code at Freecode and/or SourceForge

See also: GMLP markupproRulessekretsWordBash