从对象属性触发时为什么PHP`__invoke`不起作用

时间:2017-01-04 09:45:19

标签: php

我想知道这是一个错误还是正常。假设我有一个具有一些神奇功能的课程:

class Foo {
    public function __toString() {
        return '`__toString` called.';
    }
    public function __get($key) {
        return '`__get(' . $key . ')` called.';
    }
    public function __invoke($x = "") {
        return '`__invoke(' . $x . ')` called.';
    }
}

然后在对象属性中创建一个实例,如下所示:

$object = (object) [
    'foo' => 'bar',
    'baz' => new Foo
];

然后测试一下:

echo $object->baz;
echo $object->baz->qux;
echo $object->baz('%'); // :(

它在最后一个回声中被打破:Call to undefined method stdClass::baz()

目前,我能做的唯一解决方案是将__invoke部分存储在临时变量中,然后将该变量作为这样的函数调用:

$x = $object->baz;
echo $x('%'); // :)

当我在数组属性中实例化类时,它工作正常:

$array = [
    'baz' => new Foo
];

echo $array['baz'];
echo $array['baz']->qux;
echo $array['baz']('%'); // :)

顺便说一句,我需要在我的对象上使用此功能来处理与API相关的事情:

$foo = (object) ['bar' => new MyClass];
  • echo $foo->bar;→应触发__toString
  • echo $foo->bar->baz;→应触发__get
  • echo $foo->bar();→应触发__invoke
  • echo $foo->bar->baz();→应触发__call

所有这些都应该返回一个字符串。

这完全可以在PHP中完成吗?感谢。

2 个答案:

答案 0 :(得分:5)

没有办法。

有问题的行很简单,错误消息会告诉您如何...尝试访问baz()对象的$object方法更合乎逻辑。
这只是解析器在看到$object->baz()

时给出的上下文

正如评论中已经提到的,你可以消除这种歧义,通过告诉它$object->baz本身就是一个需要首先执行的表达来帮助解析器:

($object->baz)('arg');

PHP本身也是一个程序,必须知道如何在执行之前执行某些操作。如果它可以盲目地在$foo->bar->baz->qux链中的每个对象上尝试每种可能的“魔法”方法,那么它就无法告诉你遇到它时的错误 - 它会无声地崩溃。

答案 1 :(得分:1)

我通过在类的__invoke方法中检测到__call方法的存在来解决我的问题。

class MyStdClass extends stdClass {
    protected $data = [];
    public function __construct(array $array) {
        $this->data = $array;
    }
    public function __get($key) {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }
    public function __call($key, $args = []) {
        if (isset($this->data[$key])) {
            $test = $this->data[$key];
            // not an object = not an instance, skip!
            if (!is_object($test)) {
                return $this->__get($key);
            }
            if (!empty($args) && get_class($test) && method_exists($test, '__invoke')) {
                // or `return $test(...$args)`
                return call_user_func([$test, '__invoke'], ...$args);
            } 
        }
        return $this->__get($key);
    }
    public function __set($key, $value = null) {
        $this->data[$key] = $value;
    }
    public function __toString() {
        return json_encode($this->data);
    }
    public function __isset($key) {}
    public function __unset($key) {}
}

因此,我没有使用(object)将数组转换为对象,而是使用:

$object = new MyStdClass([
    'foo' => 'bar',
    'baz' => new Foo
]);