[stupid "message" here]

I Reckon This Must be the Place, I Reckon

A Place that is small, efficient and fast or something.

PHP Debug API

All error conditions should output a helpful diagnostic message. A stack trace ain't helpful...

PHP Debug is a single file API for tracing PHP applications. Not a class. Not a Logger (tho maybe it should be).

You can add, with minimal impact, diagnostics to be displayed upon program termination. Each diagnostic contains file, function and caller function information as well.

Here is the PHP Debug code archive. But read the README first. There is a Go debug, and a Perl debug, to be here soon.

Just include the DEBUG.PHP file and add debug() calls throughout your code similar to var_dump(). However, the diagnostic messages are stored and displayed as a list at program termination. The diagnostics can be disabled and controlled in other ways.

It's simple yet very versatile and includes demonstration/testing code. Though at about 1,000 lines it's kinda big (one thousand lines is really frackn' big!), but it's also kind sloppy, and in parts, kinda lame, so there's that.

At least all you have to do is include it and call it's function.

Screen Shots

Okay

Calling this code a "DEBUG" API is a bit of a misnomer. While it does allow for a way to locate coding errors, it is really just useful for a way of seeing what the code is doing (outside of a "real debugger").

The "Debug API" outputs to the Display, not a "log" or file.

I'll be using example code to demonstrate the PHP DEBUG API called YerCode. (I'd call this "Programming by Example" but that phrase has been changed from showing people how to use code, to telling programs how to write code. sheesh)

First thing is to include the API, which creates a function aptly (kind of) named debug().

PHP code /* somewhere at the top of YerCode */

include PATHTO.'/debug.php';
debug(__FILE__);

/* rest of YerCode */

Which does nothing.

Using

To make the API do something it needs to be turned on. (It's okay, it likes it!)

PHP code /* somewhere at the top of YerCode */

define('DEBUG_LOG',1);          /* turn it on */
include PATHTO.'/debug.php';
debug(__FILE__);

/* rest of YerCode */

Then, at program termination, there can be something not too unlike this:

(index.php,28,-)(config.php,5,config) /srv/www/htdocs/yercode/config.php

Which ain't that useful, as all it's saying is, "We are here! We are here!". However, let's say part of YerCode didn't work as expected, related to a particular variable. So you add this:

PHP code /* somewhere inside YerCode's wpshit */

function wptext($text) {
   
// ...
   
debug($characters);
   
// ...
}

And then there is the diagnostic like:

(index.php,28,-,wpshit.php,5,wptext) array ( 0 => '---', 1 => '--', 2 => 'xn–', '...',)

And yer like, "Ugh! That there should be a ex en dash dash, not a ex en entity!". So you change the code to swap the bad from-to array values and save the project!

Yeah

That's a pretty lame example, but it shows the basic usage quite plainly: One puts tiny magnifying glasses throughout the code allowing you to "see" deep inside the code, in realtime. Otherwise, as Zend says:

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

Yeah But

The Postman taps twice on the window, "Yer just swapping var dump with debug!"

Yeah, but with extra stuff! Like turning them on or off during runtime:

PHP code /* somewhere at the top of YerCode */

if ($_SERVER['SERVER_NAME'] == 'localhost') {
   
define('DEBUG_LOG',1);
}
include 
PATHTO.'/debug.php';

/* in a function in YerCode */

function YerFunction() {

    if (
$_SERVER['SERVER_NAME'] == 'localhost') {
       
debuglog(1);
       
// if local we're on! otherwise you get nothin!
   
}
}

And since our "debug" diagnostics are issued upon program termination (all good things must come to an end!) sometimes that can interfere with HTML's regular duties. But that can be fixed.

PHP code /* oh boy, we got a issue from YerCode */

function YerIssue($yerfile) {
   
debuglog(0);
   
header('Content-Type: text/plain');
   
readfile($yerfile);
    exit;
}

/* oh no, we got a leave by YerCode */

function YerLeaving() {
   
debuglog(0);
   
header("Location: http://www.yerwebsite.com/");
    exit;
}

That last example does show a flaw (kind of), but not really a flaw in this code by in how PHP works.

Each call to debug() (by default) stores each diagnostic message internally to the API. The diagnostic messages are displayed at program termination. The API can only do this by a shutdown function. And so, if YerCode has to do something like right above such output needs to be not done. So an apparatus for signaling that state/condition needs to be implemented.

The only way to implement such a semaphore is by pre-cognition – that is knowing what parts of YerCode needs such "turn shit off" thingies – and modifying YerCode to support it.

(There is no magic here. This is PHP.)

More

So far two API functions have been shown, to make a diagnostic and to turn shit on and off. Well, there's more!

But Wait

"Hey. Look at me! I'm a browser!"

(push)

I forgot something... YerCode can run in a Terminal or a Browser. So it needs to know that because one uses ANSCII codes (that's short for ANSI/ASCII) and one uses Markup. Luckily PHP has a flag for which mode YerCode is in (it's called $argv).

And Then

(push)

There is a way to format the output, like for HTML, it can be like this:

<div id='debugmsgs'><pre>
<b>message log:</b>
(index.php,28,-)(config.php,6,config) /srv/www/htdocs/yercode/config.php
</pre></div>

For Terminal it's just:

message log:
(index.php,28,-)(config.php,6,config) /srv/www/htdocs/yercode/config.php

There's other stuff, but I'll not push it.

More

(pop)(pop)

Here're the functions exposed so far.

debug($msg$arg "" )
debuglog($log)

Later on this doc will be updated to the newer PHP function documentation style...

Others are:

debugdump($dump 0$data "")
debugfiles($files ""$nofiles "")
debugfuncs($funcs ""$nofuncs "")
debugcalls($depth 1$count 2)
debugexit($msg ""$status 0)

Briefly, they are to: add some data to diagnostic output; indicate source files (by name) to include or exclude from outout; indicate functions (by name) to include or exclude from outout; produce a call trace string (such as in the output examples); exit with a message and/or status.

Briefly, "dump" is not quite right (it ain't a "hex dump"). It's related to var_dump(), but it's really just "display data".

PHP code /* start of YerCode */
define('DEBUG_LOG',1);
include 
PATHTO.'/debug.php';
debugdump(DBG_DUMP_SERVER);
debug('','.version');
message log:
(index.php,5,-) Debug v2.3.6 (/srv/www/htdocs/debug)

data dump:
$_SERVER ( HTTP_HOST => localhost,
...
 REQUEST_METHOD => GET,
 QUERY_STRING => dump=1&data=_SERVER,
 REQUEST_URI => /debug/index.php?dump=1&data=_SERVER,
 SCRIPT_NAME => /debug/index.php,
 PHP_SELF => /debug/index.php,
 REQUEST_TIME_FLOAT => 1667384339.601,
 REQUEST_TIME => 1667384339,
 )

There are two ways to tell DEBUG what to do: 1) defines of DEBUG_<NAME> for loading; functions with defines of DBG_<NAME> for runtime. The above could be:

PHP code /* start of YerCode */
define('DEBUG_LOG',1);
define('DEBUG_DUMP',7);
include 
PATHTO.'/debug.php';
debug('','.version');

Which is cool, but one needs to know values. ("What does '7' mean?") So, if you're afraid of 7 (it did eat 9!) there's a function to use instead!

Oh, and another thing: the debug() function's $arg argument is really weird. When used it's usually a DGB_ constant; but can be a string; and if a string with a leading dot ("."), it's a "dot command" (remember Wordstar? sure you do).

The .version dot command tells DEBUG to emit it's version and location. (Added so's I could tell which version I was using when there were more than one...) Most dot commands effect the message value, that one don't.

Right

So the "message" debug() emits (remember, it's debug($msg,$arg='')), has a "two back" call string of "file,line,function", i.e. the caller and the callee (is that right? yeah, I think so). Two is usually sufficient, but sometimes... So there are extra extras.

PHP code <?php
/* example from DEBUG example code that akchully werx */
define('DEBUG_LOG',1);
include 
'debug.php';

depthtest();

function 
depthtest() {
       
funca();
}
function 
funca() {
       
funcb();
}
function 
funcb() {
       
debug('default');           # go normal
       
debug('up',DBG_UP);         # go back
       
debug('deep',DBG_DEEP);     # go deep
}
message log:
(example.php,12,funca)(example.php,15,funcb) default
(example.php,9,depthtest)(example.php,12,funca) up
(example.php,6,-)(example.php,9,depthtest)(example.php,12,funca)(example.php,17,funcb) deep

So one can "see" them all. (Up to seven of them anyway.)

Well

This may actually turn out to be useful!

Oh But Yeah Know

Thing is, is this thing was created just for me to "see" what's going on on the inside of not YerShit but of MyShit. If you get my meaning.

Thing is, I am easily confused. (What's that?) Yeah, hey, what can I say? I am easily confused. (There. Said it.)

That said, often, with MyShit, things get weird after a change is made. Even after a small change. Even after a seemingly very slight change. But first, what kind of changes can occur? Code Changes can be classified:

  1. Do nothing.
  2. Flow change.
  3. Data change.

A Do Nothing change is controversial, in that one school says that any change effects everything and that every change has sideeffects... But here is an example:

PHP code /* add these and only these lines to the MyShit Application */
function donothing() {
   
// do nothing
}

Did that really do nothing to execution of the MyShit Application? Don't answer. You might be wrong.

Okay, okay. I'll get to the point. Let's add just this function to MyShit:

PHP code function toMyShit() {
    include 
"debug.php";
}

With no other changes, that "code" does not even get executed. But say it did. Since in a function, in PHP, it's a bit different than if out. It's variable scope. Right? Maybe. As PHP says, "all functions and classes defined in the included file have the global scope." But so too all defined constants. So too declared globals. And all PHP superglobals can be modified.

But I bring all that up just to make the point that all DEBUG creates are DEBUG_ and DBG_ prefixed defines, and debug prefixed functions. So DEBUG can be included in a function and work as defined. Indeed, this is a preferred way. For instead of just making the debug() function "do nothing" if it's told to do nothing, which is "Way One" of Usage. The main debug() function is kinda like:

PHP code function debug() {
    if (!
DEBUG_LOG) {
        return;
    }
   
// ...
}

But, without further commentary, DEBUG can be used or not in a slightly different way ("Way Two"), by use of different defined constant:

PHP code function debugload() {
    if (
defined(DEBUG_PHP_API)) {
        include 
DEBUG_PHP_API.'/debug.php';
       
debuglog(DEBUG_API_LOG);
    } else {
        function 
debug(){}
        function 
debuglog(){}
    }
}

That way DEBUG ain't doing nothing to YerCode but for however many "do nothing" function calls there are that, um, do nothing.

Ways and Means

There are two categories of DEBUG defines, Control and Options. Control informs (or modifies) how DEBUG works; Options adjusts (or shapes) how DEBUG looks. The first can be defined before the API is included, the other are constants for runtime and are options for the API.

The include constants are used this way in the API:

PHP code // basic options
defined('DEBUG_EMPTY') || define('DEBUG_EMPTY',0);
defined('DEBUG_CALLS') || define('DEBUG_CALLS',2);
defined('DEBUG_TRUNC') || define('DEBUG_TRUNC',100);
defined('DEBUG_ESCSTR') || define('DEBUG_ESCSTR',1);
defined('DEBUG_MSG_SEP') | define('DEBUG_MSG_SEP',' ');

The others are just yer basic constants:

PHP code // debug() $arg constants
define('DBG_HERENOW',1);
define('DBG_ALWAYS',2);
define('DBG_RETURN',3);
define('DBG_DEEP',7);
// debuglog() $log constants
define('DBG_HTML_COMMENT',99);
define('DBG_MESSAGES',100);

Which really should be enmumerations; which really should be bit flags; which.... Well, I think you get the picture.

The former are kind of like for API configuration. Instead of INI, Json, RDF, XML or what have you you just define defines before including the include.

This vs Next

The README describes all that in greater detail. This rambling, jumble of text is just an introduction to the DEBUG API that hopefully will be obsolete by the time anyone actually is reading it.

The next thing to debug (pun not intended) is a smaller version, say, half the size, but with many more features. I'm Working On It™

Naming Names

Using someone else's shit, er, I mean code, in yer code, can cause name conflicts. One CMS I tried (long time ago) had it's own function named debug() for example. And so there's something named "namespace" to change all an API's names (well, classes, interfaces, functions and constants) all at once or something. PHP uses a name prefix with a slash to re-name yer shit (well, my shit) via a single namespace keyword.

Say I named this space to Debug. You'd make calls to Debug\debug(). And then no name collusion! Drat. The DEBUG API breaks most unfortunately.

Two off the bat things. First, when PHP says "constants" pertaining to a namespace they mean const constants and not define constants. So all of Debug's defines have to change – which they can and that can be a Good Thing.

Second is Debug's (internal) use of register_shutdown_function(), which gets a name of something suddenly undefined (needs to be Debug\debug_msgs). That two ain't that bad.

But isn't all that just like renaming every exported function and constant to have the prefix "debug_"?

Classy Won't Cut It

I should not even address creating a debug class. (But of course I will.) Yes, a "class" has benefits, though mostly syntactically. We'd start with a like this thing:

PHP code class Debug {
    private 
$log 0;
    public function 
debug($msg$arg 0) {
        if (!
$this->log) return;
       
// ...
   
}
    public function 
debuglog($log NULL) {
        if (
$log === NULL) return $this->log;
       
$this->log $log;
    }
   
// ...
}

That's actually is an improvment over the straight API DEBUG code (you'd have to compare to see, but it is). However, the syntax part is in usage, not implementation, which is like:

PHP code include PATHTO.'/debug.php';

$debug = new Debug;
$debug->debuglog(1);

function 
sumfunction($var) {
   
$debug->debug($var);
   
// ...
}

Which is a declaration of $debug and the addition of the prefix $debug-> on the functions. That's just a new syntax.

But it don't work. Because I left something out. For the above to work it needs be like:

PHP code function sumfunction($var) {
    global 
$debug;
   
$debug->debug($var);
   
// ...
}

There is nothing really wrong there. It ain't like forgetting the global keyword could cause an inadvertant runtime error. But this API was designed to, and is meant to, be used or not to be used, to be added and removed as code develops. (Adding and removing being the basic definition of code development.)

Having to type in global $debug; before adding $debug->debug(); ain't much more than just adding debug();, but surely you see my point. A class, here, ain't gonna improve nothin.

Then there is the question of what to do with the constants. I mean, can this still be valid?

PHP code define('DEBUG_LOG',1);
include 
PATHTO.'/debug.php';

To me, no. Clearly. For then DEBUG_LOG is outside of the class entirely. But double of course, of course we can just use $debug->debuglog(1);.

But the DEBUG API has several more such "pre-defines" to control it's initialization. So a Class is not just a syntax, and more than a paradigm, it's a different architecture. One that would require the replacing of several constants with several setup function calls.

Which ain't not good! It's just that it's way different than the basic design.

A DEBUG Class then would just be different. But then, however one implements it, there remains that reliance on the $debug global that has to be "passed around".

Then there could be a "hide MyShit" thing:

PHP code /* somewhere in YerCode */

function debugload() {
global 
$debug;

    if (
defined(USE_DEBUG_PHP_API)) {
        include 
PATHTO_DEBUG_PHP_API.'/debug.php';
       
$debug = new Debug;
        if (
$_SERVER['SERVER_NAME'] == 'localhost') {
           
$debug->debuglog(1);
        }
    }
}

function 
debug($msg$arg 0) {
global 
$debug;
    if (
$debug) {
       
$debug->debug($msg,$arg);
    }
}

function 
debuglog($log NULL) {
global 
$debug;
    if (
$debug) {
        return 
$debug->debuglog($log);
    }
}

But that's simply full circle back to the API. And since that's all we want, having some class ain't gonna do us no good.

And getting back to what started all this (part of this) diatribe is, what about YerCode includes the DBEUG API and there's some name collusion? Just change the name in the API!

  1. You'll see later that the number seven can be changed. (Some numbers do not like seven.) But if you've read down to here you may already know that!
  2. What about just changing a single space in a single comment?
  3. Say there was a test for get_defined_functions() being a certain number which changed program execution?
  4. Which includes $GLOBALS, PHP's "window to everything".
  5. Usually I use capitalized words for definitions; here they are for two important but not quite "defined" technological things: Control and Options. (Or, Command and Control.)
  6. Evolutionary code can lead to Programmed Into a Corner syndrome: "If bitfield flags were used from the start..." While one cannot prevent what is not forseen, one can just change YerCode.
  7. This text was really written for me, not for you.
  8. Clearly Maniacal System, better known as Content Management System.
  9. And, of course, if YerCode is bigly big, which means huge, or massive, the odds that there's a namespace named namespace ain't zero.
  10. It'll be a compile-time error.
  11. But that is not the question.
  12. And that is the (right) question.
  13. So there'll be some manual labor to upgrading. I ain't partial to giving the Internets root access to my computer, so this'll remain Just An API that will have to be *shudder* manually maintained.

--