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

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

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

The Function

To enable the debug() function's output, you 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.
-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 the $GLOBALS['debug_msgs'] variable, 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 value:

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

With some appropriate CSS the text can displayed however one sees fit.

NOTE: The code should call debuglog() to enable debug output only if logged in as an Administrator so that no other visitor sees the output.

The Arguments

The debug() function 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 (a full list is below). Arrays are imploded into a string with associative arrays handled correctly.

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 debug() 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 HTML in any 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 options and their default values are:

    msg       "<b>message log:</b>"
    open      "\n<div style='background:#fff;color:#000;'><pre>"
    close     "</pre></div>"
    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, msg 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 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 the _debug() function is called without a value, i.e. _debug('newline'), that option's current value is returned.

There are a few "internal" functions (all begining with an underscore) that may be of use outside of debug; hopefully they are written well enough to be self-documenting.

Debug Defines

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

    DEBUG_LOG
    DEBUG_MSG
    DEBUG_OPEN
    DEBUG_CLOSE
    DEBUG_EMPTY
    DEBUG_CALLER
    DEBUG_NEWLINE
    DEBUG_ENTITIES

DEBUG_LOG is used to set the log level; same as debuglog(DEBUG_LOG).

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)

Passing an empty string, debug("") results in just a "breadcrumb", useful to follow code flow.

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.

Special Output

The following list is how debug() interprets special data.

    Type          Output
    TRUE          "(true)"
    FALSE         "(false)"
    object        "Object: ".get_class($arg)
    resource      "Resource: ".get_resource_type($arg)
    empty string  "''"

Caveat

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

        if (debug()) 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's 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.