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

Download: PHP DEBUG Archive.

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:

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.

Why Use This API?

There is only one valid reason to use this code, and that is for when you do not have a Graphical Development Environment within which you can actually "debug code" with breakpoints, variable watch, stacktrace watch, etc. Like Microsoft's "Visual Studio Code".

The other use is for when you are investigating code running remotely, like Website code. (The API can be turned on and off.)

It's small and simple yet very versatile and includes demonstration/testing code.

(It's grown to over 800 lines - minus the excessive amounts of comments. But this version is probably the peak of development - though I said that last time - with only minor fixes or enhancements from now on.)

Code Status

Latest version is 2.3.5. See Change Log. (March, 2021.)

This code is horrible. Seriously. It was really small at first, but has evolved into a bowl of spaghetti, thrown against the wall, and all dried up. The intertwining of code for Terminal vs. HTML is abombinable, the debug() formating options are horrendous, how debug() uses runtime options (_debug() data) is pathetic.

All of the options and other features were added over the course of using the API, not really as features designed in from the beginning.

But it does work. (I am constantly working on it as I actually use it.)

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

There is a Go debug and a Perl debug for smaller implementations, soon to be stored where you got this...

Code Setup

Use http://localhost/debug/index.php to test the code.

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:

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:

Which sets the debug logging value. It can be called a second time to turn logging off (or to change the type of output). (One can use debuglog(1); after including the code instead of defining DEBUG_LOG before including.)

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:

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.

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

debuglog(DBG_HTML_COMMENT);
header("HTTP/1.1 404 Not Found");
readfile("404.html");
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 in an evolutionary manner.

The code has the following problems:

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:

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

Change Log

Version 2.3.6

debug.php

Version 2.3.5

debug.php

Known Bugs

With debug log value of DBG_HTML (diagnostics in HTML comments) the output of the error handler is not fully within them.

Version 2.3.4

debug.php

Version 2.3.3

debug.php

Version 2.3.2

debug.php

debug_config.php

--

oldest change log entries removed with each new release