Whodunnit

Ever needed to know who called that function or class method resulting in your app going horribly wrong? Try debug_backtrace().

Dropping this in the function where the problem was found, you get to see the call stack which led up to the function call.

The call stack is essentially all the currently active functions / methods which have not yet returned to the point where they were called. It’s easiest to see by example.

Let’s say there’s some code you have where one function calls another which calls another like;


function hello($name) {
echo "Hello $name
";
}

function formatName($name) {
$name = strtolower($name);
$name = ucfirst($name);
hello($name);
}

function displayMessage($name) {
formatName($name);
}
?>

So if displayMessage() is called, it calls formatName() which itself calls hello() (OK this is a dumb example but illustrates the point).

Now a simple controller for the above functions;


if ( isset($_GET['name']) ) {
displayMessage($_GET['name']);
} else {
?>

Enter your name

}
?>

So far so good. But let’s say in a few weeks, you modify the formatName() function and, by mistake, insert a bug;


function formatName($name) {
$name = strtolower($name);

if ($name == 'ted') {
$name .= ' (nice to see you)';
} else {
$name = ucfirst($name);
}
hello($name);
}

Although Ted is meant to receive an extra friendly message, the first letter of his name is now no longer getting switched to upper case.

Not realising there’s a problem, a few days later you’re getting angry calls from some customers about the first letter of their name being lower case. Everyone else seems perfectly happy so you’re somewhat perplexed.

Due to the sheer complexity of the code, finding just where the problem lies is tricky so instead you employ debug_backtrace() in the hello() function, like;


function hello($name) {
if ( $name != ucfirst($name) ) {
// In reality, log to some log file...
echo "

";
         print_r(debug_backtrace());
         echo "

";
}
echo "Hello $name
";
}

Now if the first letter of the name is not uppercase, you’ll get to see the call stack. Here’s how it looks;


Array
(
[0] => Array
(
[file] => /home/hfuecks/public_html/debug.php
[line] => 20
[function] => hello
[args] => Array
(
[0] => ted (nice to see you)
)

)

[1] => Array
(
[file] => /home/hfuecks/public_html/debug.php
[line] => 24
[function] => formatname
[args] => Array
(
[0] => Ted
)

)

[2] => Array
(
[file] => /home/hfuecks/public_html/debug.php
[line] => 28
[function] => displaymessage
[args] => Array
(
[0] => Ted
)

)

)

Each element of the first order array shows you the function called, element 0 being the point where debug_backtrace() was called while those below it being the functions called prior to it (i.e. displayMessage() was called first, so it appears last). Within the second order arrays, you get to see the name of the function called, the file and line number where the function is defined and the arguments is was given.

In the above trace, you can see that the formatName() function was given the string argument ‘Ted’ but within the hello() function, it’s now in lower case. Suddenly you realise what happened…

Tools like XDebug provide more powerful debugging facilities but debug_backtrace() can be handy in environments where you don’t have the option of adding your own PHP extensions and can be worthwhile detail to add to your error logs. You need PHP 4.3.x+ to use it BTW.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Chris Vincent

    That’s insanely useful. Thanks for posting that!

  • http://www.rideontwo.com z0s0

    One of the things I find a bit frustrating is that you cannot (AFAIK) trap fatal errors with this method.

    For example, a common bug I encounter is when an object passed to some client code does not fulfill the required interface, and a fatal error ensues when the client code attempts to call a non existent method.

    Guess I just need to write stricter type checking throughout, really.

    Oh hey Harry, I should be landing in Zurich around the 2nd of July. We’ll need to catch up if you’re around!

  • http://www.phppatterns.com HarryF

    For example, a common bug I encounter is when an object passed to some client code does not fulfill the required interface, and a fatal error ensues when the client code attempts to call a non existent method.

    Guess for that specific problem you could use reflection – something like method_exists() and avoid making the call if it’s not found, while capturing the trace.

    There is that general workaround for catching fatal and parse errors described here here but, sadly, calling debug_backtrace() from the fatal_error_handler() he defines returns nothing useful, the call stack having emptied by the time it got there.

    Oh hey Harry, I should be landing in Zurich around the 2nd of July. We’ll need to catch up if you’re around!

    Definately! Should be around – will be in touch.

  • http://www.sitepoint.com/ mmj

    It’s worth pointing out that debug_backtrace() is supported only on PHP version 4.3 and above. Just in case you need to code for portability, or your host is a bit slow to upgrade.

  • http://www.renalias.net phunkphorce

    I’ve been aware of this feature for some time and I have to say that it’s very useful, specially when you have lots of classes that class each other’s methods. I have also found that it is particularly useful if we set a custom error handler that will show the stack trace every time there is an error.

    Have a look at this:

    function printStackTraceOnError()
    {
    if( function_exists(“debug_backtrace”)) {
    $info = debug_backtrace();

    print( “– Backtrace –
    ” );
    foreach( $info as $trace ) {
    if( ($trace["function"] != “_internalerrorhandler”) && ($trace["file"] != __FILE__ )) {
    print( $trace["file"] );
    print( “(“.$trace["line"].”): ” );
    if( $trace["class"] != “” )
    print( $trace["class"].”.” );
    print( $trace["function"] );
    print( “
    ” );
    }
    }
    print( “
    ” );
    }
    else {
    print(“Stack trace is not available
    “);
    }
    }

    Finally, the only thing we have to do is tell PHP to use our error handler instead of the default one:

    $old_error_handler = set_error_handler( “printStackTraceOnError” );

    Hope this helps :)

  • Dave

    That has solved alot of my problems

  • Portland Bill

    Thanks Harry & phunkforce. I find the dump of debug_backtrace so close to unreadable that I’ve altered phunkforce’s script to format it. Maybe somebody else will find it useful:


    define ("oneLine",
    "%s: %s");

    function printBacktrace () {
    echo ' Backtrace: ';

    if (!function_exists ("debug_backtrace"))
    echo 'No backtrace available';
    else {
    $index = 0;
    $callStack = debug_backtrace();

    foreach ($callStack as $key => $call) {
    if ($index++) {
    echo "";

    if ($btoRep = backtraceOnlyReport ($call))
    print ($btoRep);
    else
    printBacktraceVariable ($key, $call);
    }
    else
    printf ("%s line %s", $call ["file"], $call ["line"]);
    }
    }

    echo '';
    }

    function printBacktraceVariable ($inputKey, $var, $indent = 0) {
    if (is_array ($var)) {
    if (worthPrinting ($var))
    foreach ($var as $key => $arr_v)
    printBacktraceVariable ($key, $arr_v, $indent + 2);
    }
    else
    if (($trimVar = trim ($var)) && strcmp ($trimVar, "->"))
    printf (oneLine, $indent, $inputKey, $trimVar);
    }

    function worthPrinting ($arr) {
    return (($arr ["file"] != __FILE__) &&
    ($arr ["function"] != "_internalerrorhandler"));
    }

    function backtraceOnlyReport ($arr) {
    if (strcmp ($arr ["function"], "debug_backtrace"))
    return null;
    else
    return sprintf ("Control passed through %s, line %d",
    $arr ["file"],
    $arr ["line"]);
    }

  • Portland Bill

    Oh dear, the site has mangled the HTML and I can’t delete it. Let’s try again:


    define ("oneLine",
    "left_angle_bracketdiv style='margin-left:%sem'right_angle_bracketleft_angle_bracketspan style='font-weight:bold'right_angle_bracket%s: left_angle_bracket/spanright_angle_bracket%sleft_angle_bracket/divright_angle_bracket");

    function printBacktrace () {
    echo 'left_angle_bracketspan style="color:red"right_angle_bracketleft_angle_bracketspan style="font-weight:bold"right_angle_bracket Backtrace: ';

    if (!function_exists ("debug_backtrace"))
    echo 'No backtrace availableleft_angle_bracketbr/right_angle_bracket';
    else {
    $index = 0;
    $callStack = debug_backtrace();

    foreach ($callStack as $key =right_angle_bracket $call) {
    if ($index++) {
    echo "left_angle_bracketbrright_angle_bracket";

    if ($btoRep = backtraceOnlyReport ($call))
    print ($btoRep);
    else
    printBacktraceVariable ($key, $call);
    }
    else
    printf ("%s line %sleft_angle_bracket/spanright_angle_bracketleft_angle_bracketbr/right_angle_bracket", $call ["file"], $call ["line"]);
    }
    }

    echo 'left_angle_bracket/spanright_angle_bracketleft_angle_bracketbr/right_angle_bracket';
    }

    function printBacktraceVariable ($inputKey, $var, $indent = 0) {
    if (is_array ($var)) {
    if (worthPrinting ($var))
    foreach ($var as $key =right_angle_bracket $arr_v)
    printBacktraceVariable ($key, $arr_v, $indent + 2);
    }
    else
    if (($trimVar = trim ($var)) && strcmp ($trimVar, "-right_angle_bracket"))
    printf (oneLine, $indent, $inputKey, $trimVar);
    }

    function worthPrinting ($arr) {
    return (($arr ["file"] != __FILE__) &&
    ($arr ["function"] != "_internalerrorhandler"));
    }

    function backtraceOnlyReport ($arr) {
    if (strcmp ($arr ["function"], "debug_backtrace"))
    return null;
    else
    return sprintf ("Control passed through %s, line %dleft_angle_bracketbr/right_angle_bracket",
    $arr ["file"],
    $arr ["line"]);
    }

  • Portland Bill

    OK, that worked. To see what I’m trying to say copy it to an editor, replace every left_angle_bracket with .

  • Portland Bill

    Rats, logarithms, and poltergeisten. The above post is mangled too. Replace left_angle_bracked with the left angle bracket character. Dealing with right_angle_bracked is left an an exercise for the reader.