Правильный способ выполнения делегатов или обратных вызовов в PHP - PullRequest
9 голосов
/ 03 января 2011

Мне нужно реализовать следующий шаблон в 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'));
    }

    ...

}    

как правильно это сделать.

приписка

Я использовал call_user_func для этой цели и, верю этому или нет, он мог вызывать частных учеников, но после нескольких недель разработки я обнаружил, что он перестал работать. Было ли это ошибкой в ​​моем коде или это было что-то еще, что заставило меня думать, что call_user_func может вызывать функции закрытого класса, я не знаю, но сейчас я ищу простой, быстрый и элегантный метод безопасного вызов своего частного члена класса из другого класса. Я сейчас ищу замыкания, но у меня проблемы с $ this внутри замыкания ...

Ответы [ 3 ]

4 голосов
/ 04 января 2011

Обратные вызовы в 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'
}
4 голосов
/ 04 января 2011

В любом случае, если кому-то интересно, я нашел единственно возможное решение с помощью ReflectionMethod.Использование этого метода с Php 5.3.2 дает снижение производительности и в 2,3 раза медленнее, чем непосредственный вызов члена класса, и только в 1,3 раза медленнее, чем метод call_user_func.Так что в моем случае это абсолютно приемлемо.Вот код, если кому-то интересно:

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

}
0 голосов
/ 17 января 2019

Со временем появляются новые способы достижения этого.В настоящее время PSR-14 разработан для этого варианта использования.

Так что вы можете найти любой из этих интересных: https://packagist.org/?query=psr-14

...