简体   繁体   中英

Find out which class called a method in another class

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?

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.

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

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

eg

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"

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.

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.

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

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'];
           }
      }
   }
}
var_dump(getClass($this)); 

在命名空间 B 中的方法中使用,这将为您提供从命名空间 A 中调用命名空间 B 中的方法的类。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM