-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathEventsTree.php
162 lines (147 loc) · 5.9 KB
/
EventsTree.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?php
/*
* This file is part of the Ariadne Component Library.
*
* (c) Muze <info@muze.nl>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace arc\events;
/**
* This class implements an event stack on which listeners can be added and removed and events can be fired.
*/
class EventsTree implements EventsTreeInterface
{
use \arc\traits\Proxy {
\arc\traits\Proxy::__construct as private ProxyConstruct;
}
private $tree = null;
/**
* @param \arc\tree\NamedNode $tree The tree storage for event listeners.
*/
public function __construct($tree)
{
$this->ProxyConstruct( $tree );
$this->tree = $tree;
}
/**
* Adds an event listener for the given event and returns it.
* @param string $eventName The event to listen for
* @param callable $callback The function to call when the event occurs.
* @return Listener
*/
public function listen($eventName, $callback)
{
return $this->addListener( $eventName, $callback, false );
}
/**
* Adds an event listener for the given event and returns it. The listener
* will trigger in the capture phase - before any listeners in the listen phase.
* @param string $eventName The name of the event to listen for.
* @param callable $callback The function to call when the event occurs.
* @return Listener
*/
public function capture($eventName, $callback)
{
return $this->addListener( $eventName, $callback, true );
}
/**
* Fires an event. If the event objects preventDefault() method has been called it
* will return false, otherwise the - potentially changed - eventData will be returned.
* @param string $eventName The name of the event to fire.
* @param array $eventData Optional. Data passed to each handler through the event object.
* @return false or $eventData - which may have been modified
*/
public function fire( $eventName, $eventData = array() )
{
// FIXME: because this now uses the tree, we can't quickly check if any event listeners have been added for this eventName
// so there should probably be a common list of all handled eventNames in the entire tree as a performance improvement
$eventData['arc.path'] = $this->tree->getPath();
$event = new Event( $eventName, $eventData );
$this->walkListeners( $event );
if ($event->preventDefault) {
$result = false;
} else {
$result = $event->data;
}
return $result;
}
/**
* Returns a new EventStack with the given path.
* @param string $path The path to listen or fire an event.
* @return EventStack a new EventStack for the given path.
*/
public function cd($path)
{
return new EventsTree( $this->tree->cd( $path ) );
}
/**
* Non-fluent api method to add a listener.
* @param string $eventName The name of the event to listen for.
* @param Callable $callback The callback method to call.
* @param bool $capture Optional. If true listen in the capture phase. Default is false - listen phase.
* @return Listener
*/
private function addListener($eventName, $callback, $capture = false)
{
$listenerSection = ( $capture ? 'capture' : 'listen' ) . '.' . $eventName;
if (!is_callable($callback)) {
throw new \arc\IllegalRequest('Method is not callable.', \arc\exceptions::ILLEGAL_ARGUMENT);
}
if (!isset( $this->tree->nodeValue[ $listenerSection ])) {
$this->tree->nodeValue[ $listenerSection ] = array();
}
$this->tree->nodeValue[ $listenerSection ][] = array(
'method' => $callback
);
$id = max( array_keys( $this->tree->nodeValue[ $listenerSection ] ) );
return new Listener( $eventName, $id, $capture, $this );
}
/**
* Non-fluent api method to remove a listener.
* @param string $eventName The name of the event the listener is registered for.
* @param int $id The id of the listener.
* @param string $path Optional. The path the listener is registered on. Default is '/'.
* @param bool $capture Optional. If true the listener is triggered in the capture phase.
* Default is false.
*/
public function removeListener($eventName, $id, $capture = false)
{
$listenerSection = ( $capture ? 'capture' : 'listen' ) . '.' . $eventName;
unset( $this->tree->nodeValue[ $listenerSection ][ $id ] );
}
/**
* Calls each listener with the given event untill a listener returns false.
*/
private function walkListeners($event)
{
$callListeners = function ($listeners) use ($event) {
foreach ((array) $listeners as $listener) {
$result = call_user_func( $listener['method'], $event );
if ($result === false) {
return false; // this will stop \arc\path::walk, so other event handlers won't be called
}
}
};
$result = \arc\tree::parents(
$this->tree,
function ($node, $result) use ($callListeners, $event) {
if ($result !== false && isset( $node->nodeValue['capture.'.$event->name] )) {
return call_user_func($callListeners, $node->nodeValue['capture.'.$event->name] );
}
}
);
if (!isset( $result )) {
$result = \arc\tree::dive(
$this->tree,
function ($node) use ($callListeners, $event) {
if (isset( $node->nodeValue['listen.'.$event->name] )) {
return call_user_func($callListeners, $node->nodeValue['listen.'.$event->name ] );
}
}
);
}
return !isset( $result ) ? true : false;
}
}