14

Problem

I am open sourcing a trait which includes the magic method __call(). During testing, I encountered a challenge when the parent class of the class using the trait contains the __call method.

What I Tried

trait SomeTrait {
    public function __call($method, array $parameters) {
        // ...
        return parent::__call($method, $parameters);
    }
}

This results in the fatal error: Cannot access parent:: when current class scope has no parent

I also tried the following, based on some other answers:

return call_user_func_array([$this, '__call'], [$method, $parameters]);

This results in a Segmentation fault: 11. I imagine because of an infinite call loop.

Question

How can I invoke the parent's __call method from within the __call method of the trait?

If it is not possible from within the trait directly, how might I invoke the parent's __call method otherwise?

Community
  • 1
  • 1
Jason McCreary
  • 71,546
  • 23
  • 135
  • 174
  • 1
    Wait... should not the classes __call override the traits __call? – Federkun May 26 '15 at 13:52
  • 1
    Not if it's inherited - see Example #2 in the [Precedence section](http://php.net/manual/en/language.oop5.traits.php). Precedence is local, trait, parent. So in my case the trait takes precedence, but I want to call the inherited method. – Jason McCreary May 26 '15 at 13:53
  • [`E_CANNOT_REPRODUCE`](http://3v4l.org/ZKStr) – Alma Do May 26 '15 at 15:04

2 Answers2

13

Please check code below:

trait SomeTrait
{
    public function __call($method, array $parameters)
    {
        // ...
        return is_callable(['parent', '__call']) ? parent::__call($method, $parameters) : null;
    }
}

class GreatParentClass
{
    public function __call($method, array $parameters)
    {
        return 'bar';
    }
}

class ParenClassWithoutCall
{

}

class ProxyClass extends GreatParentClass
{

}

class FooClass extends ProxyClass
{
    use SomeTrait;
}

class BarClass extends GreatParentClass
{
    use SomeTrait;
}

class BazClass extends ParenClassWithoutCall
{
    use SomeTrait;
}

class SomeClassWithoutParent
{
    use SomeTrait;
}

$class = new FooClass();
var_dump($class->foobar());

$class = new BarClass();
var_dump($class->foobar());

$class = new BazClass();
var_dump($class->foobar());

$class = new SomeClassWithoutParent();
var_dump($class->foobar());

This will print:

string(3) "bar"
string(3) "bar"
NULL
NULL

Sandbox with the code to check: http://3v4l.org/R8hI3

EDIT: I think this will cover all possibilities.

Arius
  • 1,387
  • 1
  • 11
  • 24
  • + Would have never guessed testing for the `__call()` method directly by passing the string 'parent'. Feels like a hack, but works. Thanks. – Jason McCreary May 27 '15 at 02:14
  • Np. Check is_callable documentation - it looks weird but in fact it's proper approach. Cheers! – Arius May 27 '15 at 09:40
1

Well the problem here is that if you use this trait in a class without a parent, this call will fail. You might consider checking if the class has a parent before making the call to the parent function. That might look like this:

trait SomeTrait {
    public function __call($method, array $parameters) {
        // ...
        if (count(class_parents($this)) {
            return parent::__call($method, $parameters);
        }
    }
}

I would also say that this may not be the best design approach. I don't think something like a trait, which is supposed to present a portable, composable set of functionality, should be changing/overriding functionality in the adopting class' inheritance chain.

Mike Brant
  • 70,514
  • 10
  • 99
  • 103