A Small yet Versatile Debug API for PHP Programs

  "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

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:

  • Can be turned off (the default).
  • Can be displayed at program termination (the default).
  • Can be displayed in HTML comments.
  • Can be displayed as a single string in a DIV.
  • Can be formatted in a variety of ways.
  • Displays call stack (file/line/function) with each message.

The result is a list of diagnostic data displayed unobtrusively and under control, providing a complete runtime view of the code being debugged.

This is a single file API and not a Class.

Code Status

Latest version is 2.3.3. (December, 2018.)

This code has grown really large and complex - over 900 lines and 26 functions. It is a very sloppy in places and needs a near complete re-write.

See Go debug and Perl debug for smaller implementations.

Also note that these APIs are meant to be modified and customized by users and used for development only and not for including in a distribution (though one can).

Code Setup

There is really only one function, with the basic way to use the code is to include the API, debug.php, and to strategically place debug() calls throughout your code.

include 'debug.php';
debug("message in diagnostic output");

If you do just that, nothing will happen and any performance hit should be very small.

Most of the debug API is controlled by defines, prefixed by DEBUG_. This provides a way to control the debug API during inclusion.

To enable debug output, define DEBUG_LOG:

define('DEBUG_LOG',1);
include 'debug.php';
debug("message in diagnostic output");

When the code terminates, the final output (to STDERR if in a terminal) will be like:

message log:
(index.php,20,-) message in diagnostic output

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

There is a "control function" to enable, disable or adjust the output during run-time.

The test program covers almost all options and output variations and should be used before installing the API: http://localhost/debug/index.php.

The Functions

The diagnostic function is:

  • debug($msg [,$arg])

And it produces all the debugging diagnostics based on $msg, with the second argument changing how the $msg diagnostic is displayed.

All PHP variable types are displayed in a meaningful way.

The second argument changes the format of the diagnostic explained later.

The log control function is:

  • debuglog($log)

Which sets the debug logging value. It can be called a second time to turn logging off (or to change the type of output).

With no arguments the debug log value is returned.

For all configuration defines and debug function options see CONFIG.md.

The Output

Here is example output from the test program:

message log:
(index.php,25,-) Debug v2.3.3
(index.php,105,-)(index.php,167,test) type tests:
(index.php,105,-)(index.php,169,test) (true)
(index.php,105,-)(index.php,169,test) (false)
(index.php,105,-)(index.php,169,test) (null)
(index.php,105,-)(index.php,169,test) (empty)
(index.php,105,-)(index.php,169,test) array ( 0 => 'a', 1 => 'b', 2 => 'c',)
(index.php,171,test)(test/test.php,4,filetest) filetest
(index.php,172,test)(index.php,194,objtest) Object DateTime
(index.php,172,test)(index.php,195,objtest) object(DateTime)#1 (3) { ["date"]=> string(26) ... } 
(index.php,180,test)(-,-,is_string) Error: is_string() expects exactly 1 parameter, 0 given

The last shows how the API's PHP error handler can capture and place PHP error messages into the output. The "objtest" messages show the two levels of detail that can be used to display an object (the 2nd one truncated for clarity).

The following list is how debug() displays it's first argument by default:

type          output

string        "$msg"
NULL          "(null)"
TRUE          "(true)"
FALSE         "(false)"
empty string  "(empty)"
array()       "array()"
array         "array ( ... )"
object        "Object ".get_class($msg)
resource      "Resource ".get_resource_type($msg)

See CONFIG.md for ways to change the output.

Data Dumps

In addition to debug messages, data can be displayed as well. The data can be internal PHP data such as $_SERVER or $_POST or user constants.

The function:

  • debugdump($dump [, $data])

Will append any data to the output, where $dump is a constant indicating what PHP data to display.

The $data option is a space delimited string for global variables or functions that return data.

See CONFIG.md for more information.

Output Interference

If debugging is enabled and there is a redirect of some kind, the diagnostic output may interfere. In those cases logging should be turned off.

    header("Location: $url");
    debuglog(0);
    exit;
    header('Content-Type: text/plain');
    header('Content-Length: '.strlen($data));
    header('Content-Disposition: attachment; filename="'.$file.'"');
    echo($data);
    debuglog(0);
    exit;

Output Modification

When there is no redirect and the output is HTML – like for an error message – and one wants to see the output clean, one can do something like:

    header("HTTP/1.1 404 Not Found");
    readfile("404.html");
    debuglog(DBG_HTML_COMMENT);
    exit;

The debug log is there to be seen by viewing the source.

Test Code

There is a browser based test program, index.php, and it's use should be self-explanatory. It lets one see some real-time output with the ability to change most options via the URL.

It's equivalent for running in a terminal is debug_test.php.

The State of the Code

This code is badly in need of a re-write. Though it's basic design is very simple – log diagnostic messages with format depending on variable type and short call trace to be displayed at termination – it grew, of course, in an evolutionary manner.

The code has the following problems:

  • The brace style is poor.
  • A few functions are not useful.
  • Some parts of it have not been fully tested.
  • There are perhaps too many ways to configure it.
  • The way the code intertwines HTML vs. terminal is terrible.
  • and more

A few recommended changes are:

  1. Remove the useless functions.
  2. Add some code for command line options.
  3. Split debug() into two parts, moving the "format" portion to a seperate function.
  4. Split the API into one for HTML and one for terminal; though it might mean duplicate code.
  5. Allow for simpler user extension/customization of the debug(), debug_datadump() and debug_msgs() functions.
  6. All while making the code smaller and just one file.

Working with Other Code

All constants used are prefixed with DEBUG_ or DBG_ and all functions with debug (with the exception of the internally used function _debug()). Only one global is defined and only under a specific condition.

Oops... There are some DMUP_ prefixed constants that need to be changed...

The following programs have been tested:

  • WordPress - no conflicts.
  • SilverScript - no conflicts.
  • Joomla - no conflicts.
  • Drupal - it has a function named debug(), this code works with it by renaming this API's function.

Drupal also, during it's install process, makes an Ajax call to itself and any debug output with that will corrupt the expected data. (See Output Interference.)

(More here later.)

Debugging WordPress

The code can be used with WordPress but there are three caveats.

First, including the API and enabling debugging in wp-config.php (the recommended file for making customizations) means that many of WordPress' files cannot be debugged as they are loaded before wp-config.php. For temporary debugging, placing code like:

    include 'debug.php';
    debuglog(1);
    debugdump(1,'wp_smiliessearch wpsmiliestrans');

at the top of the file containing the code to debug works fine.

When debugging a function that is called many times, like wptexturize(), place the debug start code at the top of the file (formatting.php) and use something like this:

    static $debug = 0;
    if ($debug == 0) {
        debug($static_characters);
        debug($static_replacements);
        $debug = 1;
    }

(If you do that you may see a flaw in the data.)

Second, I think the test for being logged in as an administrator is is_super_admin(), but I have not tried that. (Including this debug code in your WordPress website might be too much work, but using it temporarily on a local setup works fine.)

Third, with the default output the diagnostics are displayed in a DIV after all other HTML, and with some themes it can mess up the display. (Viewing the page source works in that case or the DEBUG_OPEN define can be modified to suit.)

--