1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Webino (http://webino.sk/) |
4
|
|
|
* |
5
|
|
|
* @link https://github.com/webino/WebinoDebug/ for the canonical source repository |
6
|
|
|
* @copyright Copyright (c) 2014-2018 Webino, s. r. o. (http://webino.sk/) |
7
|
|
|
* @license BSD-3-Clause |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace WebinoDebug\Service; |
11
|
|
|
|
12
|
|
|
use ReflectionFunction; |
13
|
|
|
use WebinoDebug\Debugger\DebuggerInterface; |
14
|
|
|
use Zend\EventManager\EventInterface; |
15
|
|
|
use Zend\EventManager\EventManager; |
16
|
|
|
use Zend\EventManager\EventsCapableInterface; |
17
|
|
|
use Zend\EventManager\SharedEventManagerInterface; |
18
|
|
|
use Zend\Stdlib\CallbackHandler; |
19
|
|
|
use Zend\Stdlib\PriorityQueue; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Class EventProfiler |
23
|
|
|
*/ |
24
|
|
|
class EventProfiler |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* Debug backtrace limit |
28
|
|
|
*/ |
29
|
|
|
const BACKTRACE_LIMIT = 6; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var DebuggerInterface |
33
|
|
|
*/ |
34
|
|
|
protected $debugger; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var SharedEventManagerInterface |
38
|
|
|
*/ |
39
|
|
|
protected $sharedEvents; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var array |
43
|
|
|
*/ |
44
|
|
|
protected $data = []; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var string |
48
|
|
|
*/ |
49
|
|
|
private $cwd; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var EventInterface |
53
|
|
|
*/ |
54
|
|
|
private $lastEvent; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var string |
58
|
|
|
*/ |
59
|
|
|
private $lastKey; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @param object|DebuggerInterface $debugger |
63
|
|
|
* @param object|SharedEventManagerInterface $sharedEvents |
64
|
|
|
*/ |
65
|
|
|
public function __construct(DebuggerInterface $debugger, SharedEventManagerInterface $sharedEvents) |
66
|
|
|
{ |
67
|
|
|
$this->debugger = $debugger; |
68
|
|
|
$this->sharedEvents = $sharedEvents; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @return array |
73
|
|
|
*/ |
74
|
|
|
public function getData() |
75
|
|
|
{ |
76
|
|
|
$this->lastEvent and $this->setEvent($this->lastEvent); |
77
|
|
|
return $this->data; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @param EventInterface $event |
82
|
|
|
*/ |
83
|
|
|
public function setEvent(EventInterface $event) |
84
|
|
|
{ |
85
|
|
|
$key = $this->createEventKey($event); |
86
|
|
|
$time = $this->debugger->timer(__CLASS__)->getDelta(); |
87
|
|
|
|
88
|
|
|
$this->lastKey and $this->data[$this->lastKey]['time']+= $time; |
89
|
|
|
|
90
|
|
|
if (isset($this->data[$key])) { |
91
|
|
|
return; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
$this->data[$key] = [ |
95
|
|
|
'time' => 0, |
96
|
|
|
'caller' => $this->createCallerTrace(), |
97
|
|
|
'event' => $this->createEventManagerCallbacks($event), |
98
|
|
|
'sharedEvent' => $this->createSharedEventManagerCallbacks($event), |
99
|
|
|
]; |
100
|
|
|
|
101
|
|
|
$this->lastEvent = $event; |
102
|
|
|
$this->lastKey = $key; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @param EventInterface $event |
107
|
|
|
* @return string |
108
|
|
|
*/ |
109
|
|
|
protected function createEventKey(EventInterface $event) |
110
|
|
|
{ |
111
|
|
|
$id = get_class($event->getTarget()); |
112
|
|
|
return sprintf('%s::%s', $id, $event->getName()); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @return array |
117
|
|
|
*/ |
118
|
|
|
protected function createCallerTrace() |
119
|
|
|
{ |
120
|
|
|
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $this::BACKTRACE_LIMIT); |
121
|
|
|
$index = $this::BACKTRACE_LIMIT - 1; |
122
|
|
|
|
123
|
|
|
if (isset($backtrace[$index]) |
124
|
|
|
&& false !== strpos($backtrace[$index]['function'], 'trigger') |
125
|
|
|
) { |
126
|
|
|
return [ |
127
|
|
|
'file' => $backtrace[$index]['file'], |
128
|
|
|
'line' => $backtrace[$index]['line'], |
129
|
|
|
]; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return [ |
133
|
|
|
'file' => null, |
134
|
|
|
'line' => null |
135
|
|
|
]; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @param EventInterface $event |
140
|
|
|
* @return array |
141
|
|
|
*/ |
142
|
|
|
protected function createEventManagerCallbacks(EventInterface $event) |
143
|
|
|
{ |
144
|
|
|
$target = $event->getTarget(); |
145
|
|
|
$name = $event->getName(); |
146
|
|
|
|
147
|
|
|
$callbacks = []; |
148
|
|
|
if (is_object($target) |
149
|
|
|
&& $target instanceof EventsCapableInterface |
150
|
|
|
) { |
151
|
|
|
|
152
|
|
|
$events = $target->getEventManager(); |
153
|
|
|
if ($events instanceof EventManager) { |
154
|
|
|
$listeners = $events->getListeners($name); |
|
|
|
|
155
|
|
|
$callbacks = $this->resolveCallbacks($listeners); |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
return $callbacks; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @param EventInterface $event |
164
|
|
|
* @return array |
165
|
|
|
*/ |
166
|
|
|
protected function createSharedEventManagerCallbacks(EventInterface $event) |
167
|
|
|
{ |
168
|
|
|
$target = $event->getTarget(); |
169
|
|
|
$id = get_class($target); |
170
|
|
|
$name = $event->getName(); |
171
|
|
|
|
172
|
|
|
$callbacks = []; |
173
|
|
|
$sharedListeners = $this->sharedEvents->getListeners($id, $name); |
174
|
|
|
if ($sharedListeners !== false) { |
175
|
|
|
$callbacks = $this->resolveCallbacks($sharedListeners); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return $callbacks; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @param PriorityQueue $listeners |
183
|
|
|
* @return array |
184
|
|
|
*/ |
185
|
|
|
protected function resolveCallbacks(PriorityQueue $listeners) |
186
|
|
|
{ |
187
|
|
|
$callbacks = []; |
188
|
|
|
foreach ($listeners as $listener) { |
189
|
|
|
if ($listener instanceof CallbackHandler) { |
190
|
|
|
$callbacks[] = $this->resolveCallbackFromListener($listener); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return $callbacks; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @param CallbackHandler $listener |
199
|
|
|
* @return array |
200
|
|
|
*/ |
201
|
|
|
protected function resolveCallbackFromListener(CallbackHandler $listener) |
202
|
|
|
{ |
203
|
|
|
$callback = $listener->getCallback(); |
204
|
|
|
$priority = (int) $listener->getMetadatum('priority'); |
205
|
|
|
|
206
|
|
|
if ($callback instanceof \Closure) { |
207
|
|
|
$id = $this->resolveCallbackIdFromClosure($callback); |
208
|
|
|
|
209
|
|
|
} elseif (is_array($callback) && count($callback) === 2 && is_object($callback[0])) { |
210
|
|
|
$id = $this->createMethodName($callback[0], $callback[1]); |
211
|
|
|
|
212
|
|
|
} elseif (is_string($callback)) { |
213
|
|
|
$id = $callback; |
214
|
|
|
|
215
|
|
|
} elseif (is_object($callback) && is_callable($callback)) { |
216
|
|
|
$id = $this->createMethodName((object) $callback, '__invoke'); |
217
|
|
|
|
218
|
|
|
} else { |
219
|
|
|
$id = 'Unknown callback'; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
return [ |
223
|
|
|
'callback' => $id, |
224
|
|
|
'priority' => $priority, |
225
|
|
|
]; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @param \Closure $function |
230
|
|
|
* @return string |
231
|
|
|
*/ |
232
|
|
|
protected function resolveCallbackIdFromClosure(\Closure $function) |
233
|
|
|
{ |
234
|
|
|
try { |
235
|
|
|
$ref = new ReflectionFunction($function); |
236
|
|
|
$file = $ref->getFileName(); |
237
|
|
|
$start = $ref->getStartLine(); |
238
|
|
|
$end = $ref->getEndLine(); |
239
|
|
|
|
240
|
|
|
return sprintf('Closure: %s:%d-%d', $file, $start, $end); |
241
|
|
|
} catch (\Throwable $exc) { |
242
|
|
|
error_log($exc); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
return ''; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* @param object $object |
250
|
|
|
* @param string $method |
251
|
|
|
* @return string |
252
|
|
|
*/ |
253
|
|
|
protected function createMethodName($object, $method) |
254
|
|
|
{ |
255
|
|
|
return sprintf('%s::%s()', get_class($object), $method); |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.