在PHP中执行委托或回调的正确方法

时间:2011-01-03 11:54:26

标签: php delegates callback

我需要在php中实现以下模式:

class EventSubscriber
{
    private $userCode;
    public function __construct(&$userCode) { $this->userCode = &$userCode; }
    public function Subscribe($eventHandler) { $userCode[] = $eventHandler; }
}

class Event
{
    private $subscriber;
    private $userCode = array();

    public function __construct()
    {
        $this->subscriber = new Subscriber($this->userCode)
    }

    public function Subscriber() { return $this->subscriber; }

    public function Fire()
    {
        foreach ($this->userCode as $eventHandler)
        {
            /* Here i need to execute $eventHandler  */
        }
    }
}

class Button
{
    private $eventClick;

    public function __construct() { $this->eventClick = new Event(); }

    public function EventClick() { return $this->eventClick->Subscriber(); }

    public function Render()
    {
        if (/* Button was clicked */) $this->eventClick->Fire();

        return '<input type="button" />';
    }
}

class Page
{
    private $button;

    // THIS IS PRIVATE CLASS MEMBER !!!
    private function ButtonClickedHandler($sender, $eventArgs)
    {
        echo "button was clicked";
    }

    public function __construct()
    {
        $this->button = new Button();
        $this->button->EventClick()->Subscribe(array($this, 'ButtonClickedHandler'));
    }

    ...

}    

这样做的正确方法是什么。

P.S。

我为此目的使用call_user_func并且信不信由它能够调用私人类成员,但经过几周的开发后我发现它已经停止工作了。这是我的代码中的错误还是其他一些让我觉得'call_user_func'能够调用私有类函数的东西,我不知道,但现在我正在寻找一种简单,快速和优雅的安全方法从其他类调用一个私有类成员。我现在正在寻找封闭装置,但是封闭内部的'$ this'有问题......

3 个答案:

答案 0 :(得分:4)

无论如何,如果有人感兴趣,我通过ReflectionMethod找到了唯一可能的解决方案。在Php 5.3.2中使用此方法会带来性能损失,并且比直接调用类成员慢2.3倍,并且只比call_user_func方法慢1.3倍。所以在我的情况下,这绝对是可以接受的如果有人感兴趣,这是代码:

class EventArgs {

}

class EventEraser {

    private $eventIndex;
    private $eventErased;
    private $eventHandlers;

    public function __construct($eventIndex, array &$eventHandlers) {
        $this->eventIndex = $eventIndex;
        $this->eventHandlers = &$eventHandlers;
    }

    public function RemoveEventHandler() {
        if (!$this->eventErased) {
            unset($this->eventHandlers[$this->eventIndex]);

            $this->eventErased = true;
        }
    }

}

class EventSubscriber {

    private $eventIndex;
    private $eventHandlers;

    public function __construct(array &$eventHandlers) {
        $this->eventIndex = 0;
        $this->eventHandlers = &$eventHandlers;
    }

    public function AddEventHandler(EventHandler $eventHandler) {
        $this->eventHandlers[$this->eventIndex++] = $eventHandler;
    }

    public function AddRemovableEventHandler(EventHandler $eventHandler) {
        $this->eventHandlers[$this->eventIndex] = $eventHandler;

        $result = new EventEraser($this->eventIndex++, $this->eventHandlers);

        return $result;
    }

}

class EventHandler {

    private $owner;
    private $method;

    public function __construct($owner, $methodName) {
        $this->owner = $owner;
        $this->method = new \ReflectionMethod($owner, $methodName);
        $this->method->setAccessible(true);
    }

    public function Invoke($sender, $eventArgs) {
        $this->method->invoke($this->owner, $sender, $eventArgs);
    }

}

class Event {

    private $unlocked = true;
    private $eventReceiver;
    private $eventHandlers;
    private $recursionAllowed = true;

    public function __construct() {
        $this->eventHandlers = array();
    }

    public function GetUnlocked() {
        return $this->unlocked;
    }

    public function SetUnlocked($value) {
        $this->unlocked = $value;
    }

    public function FireEventHandlers($sender, $eventArgs) {
        if ($this->unlocked) {
            //защита от рекурсии
            if ($this->recursionAllowed) {
                $this->recursionAllowed = false;

                foreach ($this->eventHandlers as $eventHandler) {
                    $eventHandler->Invoke($sender, $eventArgs);
                }

                $this->recursionAllowed = true;
            }
        }
    }

    public function Subscriber() {
        if ($this->eventReceiver == null) {
            $this->eventReceiver = new EventSubscriber($this->eventHandlers);
        }

        return $this->eventReceiver;
    }

}

答案 1 :(得分:4)

PHP中的回调与大多数其他语言中的回调不同。典型语言将回调表示为指针,而PHP将它们表示为字符串。字符串或array()语法与调用之间没有“魔力”。 call_user_func(array($obj, 'str'))在语法上与$obj->str()相同。如果str是私有的,则呼叫将失败。

您应该简单地将事件处理程序设为公共。这具有有效的语义含义,即“打算从我的课外调用。”

此实现选择还有其他有趣的副作用,例如:

class Food {
    static function getCallback() {
        return 'self::func';
    }

    static function func() {}

    static function go() {
        call_user_func(self::getCallback());  // Calls the intended function
    }
}

class Barf {
    static function go() {
        call_user_func(Food::getCallback());  // 'self' is interpreted as 'Barf', so:
    }                                         // Error -- no function 'func' in 'Barf'
}

答案 2 :(得分:0)

随着时间的流逝,有新的方法可以实现这一目标。 目前正在起草PSR-14来处理此用例。

因此,您可能会发现以下任何有趣的东西: https://packagist.org/?query=psr-14