3v4l.org

run code in 150+ php & hhvm versions
Bugs & Features
<?php error_reporting(-1); class Slot { /** * @var callable */ public $listener; /** * @var boolean */ public $once; /** @var array */ public $params = array(); /** * @var Signal */ private $signal; function __construct(Signal $signal, $listener, $once) { $this->listener = $listener; $this->once = $once; $this->signal = $signal; } public function execute(array $values) { $resolver = $this->signal->getListenerResolver(); if ($resolver) { $callback = $resolver->resolve($this->listener); } else { $callback = $this->listener; } $args = array_merge($values, $this->params); return call_user_func_array($callback, $args); } public function getListener() { return $this->listener; } public function getOnce() { return $this->once; } } class SlotList { /** * @var Signal */ private $signal; /** * @var Slot[] */ private $slots = array(); function __construct(Signal $signal) { $this->signal = $signal; } public function add(Slot $slot) { return $this->slots[] = $slot; } public function count() { return count($this->slots); } public function execute(array $values) { $returnResultMatcher = $this->signal->getReturnResultMatcher(); foreach ($this->slots as $key => $slot) { if ($slot->once) { unset($this->slots[$key]); } $result = $slot->execute($values); if ($returnResultMatcher && $returnResultMatcher->match($result)) { return $result; } } } /** * @param callable $listener * @return Slot */ public function find($listener) { foreach ($this->slots as $slot) { if ($slot->listener === $listener) { return $slot; } } } /** * @param callable $listener * @return Slot */ public function remove($listener) { foreach ($this->slots as $key => $slot) { if ($slot->listener === $listener) { unset($this->slots[$key]); return $slot; } } } } interface ListenerResolverInterface { public function resolve($listener); } interface ListenerResolverAwareInterface { public function getListenerResolver(); public function setListenerResolver(ListenerResolverInterface $listenerResolver); } class SimpleListenerResolver implements ListenerResolverInterface { public function resolve($listener) { if (is_array($listener)) { list($class, $method) = $listener; } else if (is_string($listener) && strpos($listener, '::')) { list($class, $method) = explode('::', $listener); } if (isset($class) && !is_object($class)) { return array(new $class, $method); } return $listener; } } interface ReturnResultMatcherInterface { public function match($result); } interface ReturnResultMatcherAwareInterface { public function getReturnResultMatcher(); public function setReturnResultMatcher(ReturnResultMatcherInterface $returnResultMatcher); } class NonNullReturnResultMatcher implements ReturnResultMatcherInterface { public function match($result) { return $result !== null; } } interface OnceSignalInterface { public function addOnce($listener); public function dispatch(); public function getNumListeners(); public function remove($listener); } interface SignalInterface { public function add($listener); } abstract class AbstractOnceSignal implements OnceSignalInterface { protected $slots; public function addOnce($listener) { return $this->register($listener, true); } public function dispatch() { if (count($this->slots) < 1) { return; } $values = func_get_args(); return $this->execute($values); } public function getNumListeners() { return count($this->slots); } abstract protected function register($listener, $once); abstract protected function execute(array $values); } class OnceSignal extends AbstractOnceSignal implements ListenerResolverAwareInterface, ReturnResultMatcherAwareInterface { /** @var ListenerResolverInterface */ protected $listenerResolver; /** @var ReturnResultMatcherInterface */ protected $returnResultMatcher; /** @var mixed */ private $owner; /** * @var array */ private $valueTypes; function __construct(array $valueTypes = array(), $owner = null) { $this->valueTypes = $valueTypes; $this->owner = $owner; $this->slots = new SlotList($this); } public function addOnce($listener) { return $this->register($listener, true); } public function getNumListeners() { return $this->slots->count(); } public function getListenerResolver() { return $this->listenerResolver; } public function setListenerResolver(ListenerResolverInterface $listenerResolver) { $this->listenerResolver = $listenerResolver; } public function getReturnResultMatcher() { return $this->returnResultMatcher; } public function setReturnResultMatcher(ReturnResultMatcherInterface $returnResultMatcher) { $this->returnResultMatcher = $returnResultMatcher; } protected function execute(array $values) { // validate value types if (isset($this->owner)) { $values[] = $this->owner; } if (empty($this->listenerResolver)) { $this->setListenerResolver(new SimpleListenerResolver()); } return $this->slots->execute($values); } protected function register($listener, $once) { $existing = $this->slots->find($listener); if ($existing) { if ($existing->once !== $once) { throw new InvalidArgumentException('Once not once!'); } return $existing; } return $this->slots->add(new Slot($this, $listener, $once)); } public function remove($listener) { return $this->slots->remove($listener); } } class Signal extends OnceSignal implements SignalInterface { public function add($listener) { return $this->register($listener, false); } } class MinimalSignal extends AbstractOnceSignal implements SignalInterface { public function __construct() { $this->slots = array(); } public function add($listener) { $this->register($listener, false); } protected function execute(array $values) { foreach ($this->slots as $key => $slot) { if ($slot[1]) { unset($this->slots[$key]); } call_user_func_array($slot[0], $values); } } protected function register($listener, $once) { foreach ($this->slots as $slot) { if ($slot[0] === $listener) { if ($slot[1] !== $once) { throw new InvalidArgumentException('Once not once!'); } return; } } $this->slots[] = array($listener, $once); } public function remove($listener) { foreach ($this->slots as $key => $slot) { if ($slot[0] === $listener) { unset($this->slots[$key]); return; } } } } echo "<pre>"; class MyListener { public function onSignal(MySignalOwner $owner) { die('<pre>' . print_r($owner, 1) . __FILE__ . ' : ' . __LINE__ . "\n"); } } class MySignalOwner { /** * @var Signal */ private $signal; /** * @return Signal */ public function getSignal() { return $this->signal ? : $this->signal = new Signal(array(), $this); } } $listener = 'MyListener::onSignal'; $listener = array(new MyListener, 'onSignal'); $owner = new MySignalOwner(); $owner->getSignal()->addOnce($listener); $result = $owner->getSignal()->dispatch();
Output for 5.3.0 - 7.1.0
<pre><pre>MySignalOwner Object ( [signal:MySignalOwner:private] => Signal Object ( [listenerResolver:protected] => SimpleListenerResolver Object ( ) [returnResultMatcher:protected] => [owner:OnceSignal:private] => MySignalOwner Object *RECURSION* [valueTypes:OnceSignal:private] => Array ( ) [slots:protected] => SlotList Object ( [signal:SlotList:private] => Signal Object *RECURSION* [slots:SlotList:private] => Array ( ) ) ) ) /in/1oXUL : 355