So you thought call_user_func() was just another way to call a PHP method...

Submitted by Frederic Marand on

In most cases, call_user_func() and call_user_func_array() are just slower but more flexible ways to call functions or methods. But it turns out they are not entirely equivalent where methods are involved. Consider this fragment, and guess the output.

<?php
/**
 * This base class defines two methods of interest, one private, one protected.
 *
 * It also includes 3 ways to invoke these methods from outside.
 */
class Base {
 
/**
   * A private base method
   */
 
private function priv() {
    echo
__CLASS__ . " Private\n";
  }

 
/**
   * A protected base method
   */
 
protected function prot() {
    echo
__CLASS__ . " Protected\n";
  }

 
/**
   * Expose the private method
   */
 
public function callPriv() {
   
$this->priv();
  }

 
/**
   * Expose the protected method
   */
 
public function callProt() {
   
$this->prot();
  }

 
/**
   * Expose either method via call_user_func().
   */
 
public function call($method) {
   
call_user_func([$this, $method]);
  }

 
/**
   * Expose either method using Reflection.
   */
 
public function reflect($method) {
   
$rm = new ReflectionMethod($this, $method);
    if (!
$rm->isPublic()) {
     
$rm->setAccessible(true);
    }
   
$rm->invoke($this);
  }
}

/**
 * Class Child redefines the methods of interest. Will overrides apply ?
 */
class Child extends Base {
 
/**
   * A child private method. Does it override the base method ?
   */
 
private function priv() {
    echo
__CLASS__ . " Private\n";
  }

 
/**
   * A child protected method. Does it override the base method ?
   */
 
protected function prot() {
    echo
__CLASS__ . " Protected\n";
  }
}

$f = new Child();

$f->callPriv();
$f->call('priv');
$f->reflect('priv');

echo
"\n";

$f->callProt();
$f->call('prot');
$f->reflect('prot');
?>

The question

So what will the output be ? Since $f is a Child instance, will it actually display "Child private" 3 times, then "Child protected" 3 times too ?

Answer on the Vulcan Logic dumper site: https://3v4l.org/Aoflo !

The results

VLD output for method callsAs you can see, the results are a bit unexpected, to say the least:

  • In all cases, invoking the protected method always invokes the child method.
  • However, the calls to the private method differ: $this->callPriv() invokes the base method, while $this->reflect('priv') invokes the child method
  • Even more interestingly, in HHVM, $this->call('priv') will diverge from PHP and invoke the base method while PHP invokes the child method

The explanation

Thanks to jbafford for this explanation on ##php.

What is actually happening is that private methods on the Base and Child classes are actualy isolated from each other, so when invoking $this->callPriv() on the instance, it is invoked in the Base context since that is where it lives, and at this point all it can see is the base method, not the child one.

To fix this, reimplement callPriv on the Child class, and it will see the child method, not the one on the parent class, as in https://3v4l.org/rriqc

And for extra insights (quoted with permission):

The _expected_ behavior would be for call_user_function to call the parent, which would make it consistent with $this->priv(), $this->$privInAVariable(), but then you'd never be able to have a child pass its private function as a callback to its parent. Where it's really bad is if you have three levels of nesting, and the middle level needs its function executed as a callback. To do that, you have to involve a closure.