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