30

Is there a way in PHP to find out what object called what method in another object.

Exmaple:

class Foo
{
  public function __construct()
  {
    $bar = new Bar();
    $bar->test();
  }
}

class Bar
{
  public function test()
  {
  }
}
$foo = new Foo();

Would there be a way for me to find out that the test method was called from the foo object?

Botto
  • 1,188
  • 4
  • 16
  • 29

8 Answers8

65

you could use debug_backtrace, a bit like this :
BTW, take a look at the comments on the manual page : there are some useful functions and advices given ;-)

class Foo
{
  public function __construct()
  {
    $bar = new Bar();
    $bar->test();
  }
}

class Bar
{
  public function test()
  {
      $trace = debug_backtrace();
      if (isset($trace[1])) {
          // $trace[0] is ourself
          // $trace[1] is our caller
          // and so on...
          var_dump($trace[1]);

          echo "called by {$trace[1]['class']} :: {$trace[1]['function']}";

      }
  }
}
$foo = new Foo();

The var_dump would output :

array
  'file' => string '/home/squale/developpement/tests/temp/temp.php' (length=46)
  'line' => int 29
  'function' => string '__construct' (length=11)
  'class' => string 'Foo' (length=3)
  'object' => 
    object(Foo)[1]
  'type' => string '->' (length=2)
  'args' => 
    array
      empty

and the echo :

called by Foo :: __construct

But, as nice as it might look like, I am not sure it should be used as a "normal thing" in your application... Seems odd, actually : with a good design, a method should not need to know what called it, in my opinion.

Pascal MARTIN
  • 395,085
  • 80
  • 655
  • 663
  • 2
    Working examples are always nice. +1. :) – Randolpho Jul 31 '09 at 18:21
  • Thanks ;-) (They are also a reason to not get the first answer ^^ but, at least, I have a bit of fun too, that way ;-) ) – Pascal MARTIN Jul 31 '09 at 18:22
  • 1
    Thank you for the example. Yes, I agree to a certain extent, that methods should not need to know, but I have some other people writing code on top of what I am writing and I wanted to make their life easier, he he. I think using debug is a bit hackish, but thank you, you have given me a good insight into the debugging function, something id never really done before. – Botto Jul 31 '09 at 18:36
  • You're welcome :-) and thanks for explaining the reason why you needed this ! (Another solution could be to consider using a debugger, like the one provided by Xdebug, that can be integrated with Eclipse PDT ; see for instance http://stackoverflow.com/questions/1199342/how-to-debug-a-php-application and its answers) – Pascal MARTIN Jul 31 '09 at 18:41
22

Here is one liner solution

list(, $caller) = debug_backtrace(false, 2);

As of PHP7 this won't work based on the docs: http://php.net/manual/en/function.list.php as we cannot have empty properties, here is a small update:

list($childClass, $caller) = debug_backtrace(false, 2);
Alex Rashkov
  • 9,833
  • 3
  • 32
  • 58
  • Wouldn't you want to write?... list(, $caller) = debug_backtrace(false, 2); //Or is it a waste of time? – JxAxMxIxN Jan 06 '16 at 06:33
  • @JxAxMxIxN that's a fair point will save some execution cycles, changed. – Alex Rashkov Jan 07 '16 at 09:04
  • 3
    love one liner men. Thanks – therealbigpepe Jul 01 '16 at 15:11
  • "Fatal error: Uncaught Meets\Service\Error\ErrorException: Undefined offset: 1 in..." if called from main script or one context only. Problem is list() which expects 2 offsets. If you put the result from debug_backtrace() into a var and print that out it become obvious :) – cottton Oct 10 '18 at 15:56
5

You could also have the calling object pass itself as an argument

e.g.

class Foo
{
  public function __construct()
  {
    $bar = new Bar();
    $bar->test($this);
  }
}

class Bar
{
  public function test()
  {
  }
}
$foo = new Foo();

I got this idea from the book "Design Patterns: elements of reusable object-oriented software" by Erich Gamma, et al, on page 278 in the discussion on the "Mediator" structural pattern.

The point of the pattern is to reduce the number of many-to-many connections between a bunch of objects/classes. You create a mediator class that all those classes treat as a hub. That way the classes don't need to know about each other. The mediator handles the interactions. For the mediator to be informed of changes in the classes it tracks, they can pass themselves as arguments, or the mediator can be implemented using the "Observer" pattern.

2018 EDIT:

I sometimes use interfaces with the above code, like this:

interface someInterface // many classes may implement this interface
{
  public function giveMeBar();
}

class Foo implements someInterface
{
  public function __construct()
  {
    $bar = new Bar();
    $bar->test($this);
  }
  public function giveMeBar() {
    return 'Bar';
  }
}
class Bar
{
  public function test(someInterface $a)
  {
    echo $a->giveMeBar();
  }
}
$foo = new Foo(); // prints "Bar"
Buttle Butkus
  • 9,206
  • 13
  • 79
  • 120
  • 1
    This was the most sensible solution I could come up with as well. Debug_backtrace seems like a hack, and there is no other way to navigate the call stack I know of in PHP. – siliconrockstar Sep 22 '16 at 04:29
2

You can probably achieve this with a debug backtrace, though this seems kind of hackish.

Your alternative option is to pass a parameter to that class and tell it where it is being called from, when you instantiate the class from within another.

Yuval Karmi
  • 26,277
  • 39
  • 124
  • 175
2

At the very least, you could use debug_backtrace and analyze that to find the calling method.

I think you should also be able to do it using the reflection API, but it's been too long since I've used PHP and I don't remember exactly how. The links should at least get you started, however.

Randolpho
  • 55,384
  • 17
  • 145
  • 179
2

@Pascal MARTIN: Yes, in normal applicacions it's probably not needed. But sometimes it could be useful. Consider an example from my own app:

There's a Controller subclass which can use a Template object to prepare its output. Every template has a name to refer it to. When a Controller needs a Template, it asks the TemplateManager for it by giving that name as a parameter. But there could be many template files with that name for different Controllers. Controlers are used as plugins, and may be written by different users, so the names used by them can't be controlled to no collide with each other. Namespaces for templates are needed. So TemplateManager, which is a factory for Template objects, needs the template name and the namespace name to locate the proper template source file. This namespace is related to the particular Controller's class name.

But, in most cases, each Controller will be using templates from its own namespace and only in rare cases from other namespaces. So specifying the namespace in each call to TemplateManager::getTemplate() each time would be a mess. It's better if namespace is optional and defaults to... the Controller which calls the TemplateManager::getTemplate()! And here's a good place for knowing the caller.

Of course the caller Controller could pass itself or its name as a parameter, but it doesn't really differ much from passing the namespace name. It couldn't be optional in either way.

But if you can know the caller, you can use that information to default the namespace automatically inside the getTemplate(), without even bothering the caller. It doesn't have to know how getTemplate() is handling it in its inside and how does it know the proper default namespace. He only needs to know that it does, and that it can pass any other namespace optionally if it really needs to.

SasQ
  • 14,009
  • 7
  • 43
  • 43
0

This function does the job without debug_backtrace :

/*
usage :
some code...
getRealCallClass(__FUNCTION__);
some code...
*/

function getRealCallClass($functionName) //Parameter value must always be __FUNCTION__
{
  try
   {
     throw new exception();
   }
  catch(exception $e)
   {
     $trace = $e->getTrace();
     $bInfunction = false;
     foreach($trace as $trace_piece)
      {
          if ($trace_piece['function'] == $functionName)
           {
             if (!$bInfunction)
              $bInfunction = true;
           }
          elseif($bInfunction) //found !!!
           {
             return $trace_piece['class'];
           }
      }
   }
}
-1
var_dump(getClass($this)); 

Used in a method in namespace B this will give you the class that called a method in namespace B from namespace A.

fballer87
  • 74
  • 9