36

Is there a compelling reason to not use debug_backtrace for the sole purpose of determining the calling method's class, name, and parameter list? Not for debugging purposes. It has the word "debug" in the function name, which makes me feel a little dirty to be using it in this way, but it fit the bill for what I needed to do (a single function that can be called from many places and needs to call the calling method from another system). It works, but is this still a bad idea? If so, why?

user712092
  • 1,988
  • 1
  • 14
  • 16
Sydius
  • 13,567
  • 17
  • 59
  • 76

7 Answers7

21

It does feel a little dirty, but as has been well documented, opined, and beaten to death elsewhere, PHP isn't a system designed for elegance.

One highly convoluted reason not to use debug_backtrace for application logic is it's possible some future developer working on PHP could decide "it's just a debug function, performance doesn't matter".

If you're interested in a "better" way of doing this, you could probably use PHP's magic constants to pass in the calling method and class name, and then use a ReflectionMethod object to extract any other information you need.

I put better in quotes because, while this would be cleaner and more correct, the overhead of instantiating a Reflection object may be greater than using the debug_backtrace function.

Alana Storm
  • 164,128
  • 91
  • 395
  • 599
  • Yes, I didn't know about __CLASS__ or __FUNCTION__ back then. Or the Reflection object. I think if I were to do it again, I would do it that way instead. – Sydius Dec 07 '08 at 01:17
  • Another thing to consider is how often you need it: Maintaining similar info in PHP code would add significant cycles to every request, so an occasional `debug_stacktrace` would certainly be preferred. In general, though, I agree with Konrad: outside of debugging/logging/unusual needs, the design shouldn't necessitate it. – Steve Clay May 28 '10 at 02:06
18

Is there a compelling reason to not use debug_backtrace for the sole purpose of determining the calling method's class, name, and parameter list?

Yes. The point is, it's generally a sign of bad design if your code requires such a tight coupling that the callee has to have these information about its caller because it breaks referential transparency. Therefore, if you feel the need to use these information, you probably should rethink your design.

The callee should not need to have these information to perform its task. The exceptions, of course, revolve around debugging, logging and more generally other kinds of code introspection (but even there, beware of it).

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 1
    The callee's task is to call the same method that called it on a different computer. It's to avoid having a different API function for every possible source, which reduced the amount of code in the system considerably. Is it still bad? – Sydius Dec 06 '08 at 20:27
  • I honestly have to say that I don't know. I can't see any way to handle this differently so this might be an appropriate case for such usage. – Konrad Rudolph Dec 06 '08 at 21:20
  • 3
    @Mawg Yes. Error handling and debugging is often very similar. And if the function shouldn’t be used at all, why would it exist? – Konrad Rudolph Jun 06 '11 at 08:30
4

The correct answer is that it is totally OK. Alan points the non elegance of PHP, so I won't reiterate his points. The reason to (not be afraid to) use debug_backtrace in runtime code is because PHP is mostly a context free language. Each function does not know about, nor can know the caller's "intention" without using magic function names (for instance in a class -- e.g. __toString()) with casting. Lets consider the case where you have a class, in which (in a method), you want to provide access to calling (outer) class's properties. It is truly messy and prone to errors to pass ($this) around through the function API. (Especially when you want to array_map or call_user_func_array.)

e.g.

<?
class a {
 public $v = 'xyz';
 function __construct($x,$y)
 {
   $b = new b();
 }
}
class b {
 function __construct()
 {
   $start = microtime();
   $x = debug_backtrace();
   $end = microtime();
   echo ($end-$start) . "\n";
   print_r($x);
   $obj = $x[1]['object'];
   print_r($obj);
 }
}
$a = new a(1,2);
?>

The debug_backtrace will give you access to the context of class a in the __construct of b. Now you can pass some state variables from the current class without passing $this around or trying to divine it from class names or kludge it by putting it into globals.

For those that are interested in the time, microtime was 2.7E-5 . Fast enough. Just don't put it in a while(1).

Beracah
  • 362
  • 2
  • 7
4

debug_backtrace is one of the PHP error handling functions. The manual encourages users to define their own error handling rules, as well as modify the way the errors can be logged. This allows you to change and enhance error reporting to suit your needs. This also implies that the performance hit from using these functions is negligible.

I think what you're doing is just fine.

mepcotterell
  • 2,670
  • 2
  • 21
  • 28
4

You said in a comment

The callee's task is to call the same method that called it on a different computer. It's to avoid having a different API function for every possible source, which reduced the amount of code in the system considerably. Is it still bad

So you want to do the following:

Assuming, of course, that you use HTTP requests to fire off the remote call.

This is a bit of a strange set up. If you are creating the system from scratch then I'd suggest trying to work around it. If you're shoehorning it into a legacy system then I suppose I understand.

I'd suggest that you always be more explicit when you can. Your use of magic stack introspection might save you a little bit of coding, but to another developer your code will be completely baffling. If'dsuggest that you pass the class and function name to the function that was previously doing the reflection. Then there is no ambiguity about what is happening.

Josh
  • 1,479
  • 10
  • 12
  • I didn't know about __CLASS__ and __FUNCTION__ when I first wrote it, or else I probably would have. I wanted something easy to copy/paste (and constant). I really wish PHP had macros (as bad as they can be abused). – Sydius Dec 07 '08 at 01:16
2

I did another performance test of debug_backtrace method. To simulate real stacks i defined several classes which call another class methods randomly - with a specified DEPTH:

<?php

const DEPTH = 30;
const BACKTRACE = false;

class TestClass
{
    public $a;
    public $b;

    public function __construct()
    {
        $foo = new Foo();
        $bar = new Bar();
        $alice = new Alice();

        $alice->a = $foo;
        $bar->a = $foo;

        $foo->a = $bar;
        $alice->b = $bar;

        $foo->b = $alice;
        $bar->b = $alice;

        $this->a = $foo;
        $this->b = $bar;
    }

    public function method($depth)
    {
        BACKTRACE ? debug_backtrace() : null;
        $obj = mt_rand(0,1) === 1 ? $this->a : $this->b;

        if ($depth > DEPTH) {
            return;
        }
        $obj->method($depth+1);

    }
}

class Foo
{
    public $a;
    public $b;

    public function method($depth)
    {
        BACKTRACE ? debug_backtrace() : null;

        $obj = mt_rand(0,1) === 1 ? $this->a : $this->b;

        if ($depth > DEPTH) {
            return;
        }
        $obj->method($depth+1);
    }
}

class Bar
{
    public $a;
    public $b;
    public function method($depth)
    {
        BACKTRACE ? debug_backtrace() : null;
        $obj = mt_rand(0,1) === 1 ? $this->a : $this->b;

        if ($depth > DEPTH) {
            return;
        }
        $obj->method($depth+1);
    }
}

class Alice
{
    public $a;
    public $b;

    public function method($depth)
    {
        BACKTRACE ? debug_backtrace() : null;

        $obj = mt_rand(0,1) === 1 ? $this->a : $this->b;

        if ($depth > DEPTH) {
            return;
        }

        $obj->method($depth+1);
    }
}


$test = new TestClass();

$startWhole = microtime(true);
for($i = 0; $i < 10000;$i++) {
    $start = microtime(true);
    $test->method(0);
    $end = microtime(true);
}
$endWhole = microtime(true);
$total = $endWhole - $startWhole;

echo 'total time: ' . $total . "s\n";

Result:

Performance (10.000 iterations / stack depth 30 / PHP 7.2):

with debug_backtrace - total time: 0.86011600494385s
without:             - total time: 0.043321847915649s 

using debug_backtrace makes this code 20x times slower.

Johannes Reiners
  • 570
  • 5
  • 11
0

I'm thinking about using the debug_backtrace for debugging mysql statements. It's a hell a lot of easier to pinpoint erroneous or slow queries when you have a header like this at the start of each query inside the database logs:

/*Called from /var/www/micimacko.php at line 28*/ SELECT count(*) FROM rofibeka;

The question remains. What about performance/reliability.

Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162
Jauzsika
  • 3,171
  • 3
  • 23
  • 32
  • 1
    Function trigger_error() does exactly what You described, plus saves the error to Apache error log. I use trigger_error(mysql_error()) to save all faults to log or show them on screen (depending if I am on production or development server). – Rauni Lillemets Aug 19 '11 at 10:47