Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
94.94% |
75 / 79 |
|
93.75% |
30 / 32 |
CRAP | |
0.00% |
0 / 1 |
Debugger | |
94.94% |
75 / 79 |
|
93.75% |
30 / 32 |
56.41 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
8 | |||
addHandlers | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addHandler | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
hasHandler | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHandler | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHandlers | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setStorage | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
hasStorage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStorage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addLogger | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getData | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getById | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getByType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
has | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
delete | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
clear | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
save | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
5.07 | |||
render | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getRequestId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
renderWithHeaders | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
generateId | |
40.00% |
2 / 5 |
|
0.00% |
0 / 1 |
4.94 | |||
count | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIterator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__set | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__isset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__unset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetSet | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
offsetGet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetExists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetUnset | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
__toString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Debug; |
15 | |
16 | use Pop\Debug\Handler\HandlerInterface; |
17 | use Pop\Debug\Storage\StorageInterface; |
18 | use Pop\Log\Logger; |
19 | use ArrayIterator; |
20 | |
21 | /** |
22 | * Debugger class |
23 | * |
24 | * @category Pop |
25 | * @package Pop\Debug |
26 | * @author Nick Sagona, III <dev@nolainteractive.com> |
27 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
28 | * @license http://www.popphp.org/license New BSD License |
29 | * @version 2.2.0 |
30 | */ |
31 | class Debugger implements \ArrayAccess, \Countable, \IteratorAggregate |
32 | { |
33 | |
34 | /** |
35 | * Debugger handlers |
36 | * @var array |
37 | */ |
38 | protected array $handlers = []; |
39 | |
40 | /** |
41 | * Debugger storage object |
42 | * @var ?StorageInterface |
43 | */ |
44 | protected ?StorageInterface $storage = null; |
45 | |
46 | /** |
47 | * Debugger request ID |
48 | * @var ?string |
49 | */ |
50 | protected ?string $requestId = null; |
51 | |
52 | /** |
53 | * Constructor |
54 | * |
55 | * Instantiate a debug object |
56 | */ |
57 | public function __construct() |
58 | { |
59 | $args = func_get_args(); |
60 | |
61 | foreach ($args as $arg) { |
62 | if (is_array($arg)) { |
63 | foreach ($arg as $a) { |
64 | if ($a instanceof HandlerInterface) { |
65 | $this->addHandler($a); |
66 | } else if ($a instanceof StorageInterface) { |
67 | $this->setStorage($a); |
68 | } |
69 | } |
70 | } else if ($arg instanceof HandlerInterface) { |
71 | $this->addHandler($arg); |
72 | } else if ($arg instanceof StorageInterface) { |
73 | $this->setStorage($arg); |
74 | } |
75 | } |
76 | } |
77 | |
78 | /** |
79 | * Add handlers |
80 | * |
81 | * @param array $handlers |
82 | * @return Debugger |
83 | */ |
84 | public function addHandlers(array $handlers): Debugger |
85 | { |
86 | foreach ($handlers as $handler) { |
87 | $this->addHandler($handler); |
88 | } |
89 | |
90 | return $this; |
91 | } |
92 | |
93 | /** |
94 | * Add a handler |
95 | * |
96 | * @param HandlerInterface $handler |
97 | * @return Debugger |
98 | */ |
99 | public function addHandler(HandlerInterface $handler): Debugger |
100 | { |
101 | $type = strtolower(str_replace('Handler', '', get_class($handler))); |
102 | if (strrpos($type, '\\') !== false) { |
103 | $type = substr($type, (strrpos($type, '\\') + 1)); |
104 | if (!empty($handler->getName())) { |
105 | $type = str_replace(' ', '-', strtolower($handler->getName())) . '-' . $type; |
106 | } |
107 | } |
108 | |
109 | $this->handlers[$type] = $handler; |
110 | |
111 | return $this; |
112 | } |
113 | |
114 | /** |
115 | * Determine if the debug object has a handler |
116 | * |
117 | * @param string $name |
118 | * @return bool |
119 | */ |
120 | public function hasHandler(string $name): bool |
121 | { |
122 | return isset($this->handlers[$name]); |
123 | } |
124 | |
125 | /** |
126 | * Get a handler |
127 | * |
128 | * @param string $name |
129 | * @return ?HandlerInterface |
130 | */ |
131 | public function getHandler(string $name): ?HandlerInterface |
132 | { |
133 | return $this->handlers[$name] ?? null; |
134 | } |
135 | |
136 | /** |
137 | * Get all handlers |
138 | * |
139 | * @return array |
140 | */ |
141 | public function getHandlers(): array |
142 | { |
143 | return $this->handlers; |
144 | } |
145 | |
146 | /** |
147 | * Set the storage object |
148 | * |
149 | * @param StorageInterface $storage |
150 | * @return Debugger |
151 | */ |
152 | public function setStorage(StorageInterface $storage): Debugger |
153 | { |
154 | $this->storage = $storage; |
155 | return $this; |
156 | } |
157 | |
158 | /** |
159 | * Determine if the debug object has storage |
160 | * |
161 | * @return bool |
162 | */ |
163 | public function hasStorage(): bool |
164 | { |
165 | return ($this->storage !== null); |
166 | } |
167 | |
168 | /** |
169 | * Get the storage object |
170 | * |
171 | * @return StorageInterface |
172 | */ |
173 | public function getStorage(): StorageInterface |
174 | { |
175 | return $this->storage; |
176 | } |
177 | |
178 | /** |
179 | * Add logger to handler(s) |
180 | * |
181 | * @param Logger $logger |
182 | * @param array $loggingParams |
183 | * @return Debugger |
184 | */ |
185 | public function addLogger(Logger $logger, array $loggingParams): Debugger |
186 | { |
187 | foreach ($this->handlers as $handler) { |
188 | $handler->setLogger($logger); |
189 | $handler->setLoggingParams($loggingParams); |
190 | } |
191 | return $this; |
192 | } |
193 | |
194 | /** |
195 | * Get all data from handlers |
196 | * |
197 | * @return array |
198 | */ |
199 | public function getData(): array |
200 | { |
201 | $data = []; |
202 | foreach ($this->handlers as $name => $handler) { |
203 | $data[$name] = ($this->storage->getFormat() == 'TEXT') ? $handler->prepareAsString() : $handler->prepare(); |
204 | } |
205 | return $data; |
206 | } |
207 | |
208 | /** |
209 | * Get stored request by ID |
210 | * |
211 | * @param string $id |
212 | * @return mixed |
213 | */ |
214 | public function getById(string $id): mixed |
215 | { |
216 | return $this->storage->getById($id); |
217 | } |
218 | |
219 | /** |
220 | * Get stored request by type |
221 | * |
222 | * @param string $type |
223 | * @return mixed |
224 | */ |
225 | public function getByType(string $type): mixed |
226 | { |
227 | return $this->storage->getByType($type); |
228 | } |
229 | |
230 | /** |
231 | * Determine if debug data exists by ID |
232 | * |
233 | * @param string $id |
234 | * @return bool |
235 | */ |
236 | public function has(string $id): bool |
237 | { |
238 | return $this->storage->has($id); |
239 | } |
240 | |
241 | /** |
242 | * Delete debug data by ID |
243 | * |
244 | * @param string $id |
245 | * @return void |
246 | */ |
247 | public function delete(string $id): void |
248 | { |
249 | $this->storage->delete($id); |
250 | } |
251 | |
252 | /** |
253 | * Clear storage |
254 | * |
255 | * @return void |
256 | */ |
257 | public function clear(): void |
258 | { |
259 | $this->storage->clear(); |
260 | } |
261 | |
262 | /** |
263 | * Save the debug handlers' data to storage |
264 | * |
265 | * @return string |
266 | */ |
267 | public function save(): string |
268 | { |
269 | foreach ($this->handlers as $name => $handler) { |
270 | // Storage debug info |
271 | if ($this->hasStorage()) { |
272 | $data = ($this->storage->getFormat() == 'TEXT') ? $handler->prepareAsString() : $handler->prepare(); |
273 | $this->storage->save($this->getRequestId() . '-' . $name, $data); |
274 | } |
275 | // Log debug events |
276 | if ($handler->hasLogger()) { |
277 | $handler->log(); |
278 | } |
279 | } |
280 | |
281 | return $this->getRequestId(); |
282 | } |
283 | |
284 | /** |
285 | * Render the debug handlers' data to string |
286 | * |
287 | * @return string |
288 | */ |
289 | public function render(): string |
290 | { |
291 | $output = ''; |
292 | |
293 | foreach ($this->handlers as $handler) { |
294 | $output .= $handler->prepareAsString(); |
295 | } |
296 | |
297 | return $output; |
298 | } |
299 | |
300 | /** |
301 | * Get current request ID |
302 | * |
303 | * @return string |
304 | */ |
305 | public function getRequestId(): string |
306 | { |
307 | if ($this->requestId === null) { |
308 | $this->requestId = $this->generateId(); |
309 | } |
310 | |
311 | return $this->requestId; |
312 | } |
313 | |
314 | /** |
315 | * Render the debug handlers' data to string with headers |
316 | * |
317 | * @return string |
318 | */ |
319 | public function renderWithHeaders(): string |
320 | { |
321 | $output = ''; |
322 | |
323 | foreach ($this->handlers as $handler) { |
324 | $output .= $handler->prepareHeaderAsString() . $handler->prepareAsString(); |
325 | } |
326 | |
327 | return $output; |
328 | } |
329 | |
330 | /** |
331 | * Generate unique ID |
332 | * |
333 | * @return string |
334 | */ |
335 | public function generateId(): string |
336 | { |
337 | if (function_exists('random_bytes')) { |
338 | return bin2hex(random_bytes(16)); |
339 | } else if (function_exists('openssl_random_pseudo_bytes')) { |
340 | return bin2hex(openssl_random_pseudo_bytes(16)); |
341 | } else { |
342 | return md5(uniqid()); |
343 | } |
344 | } |
345 | /** |
346 | * Method to get the count of the handlers |
347 | * |
348 | * @return int |
349 | */ |
350 | public function count(): int |
351 | { |
352 | return count($this->handlers); |
353 | } |
354 | |
355 | /** |
356 | * Method to iterate over the handlers |
357 | * |
358 | * @return ArrayIterator |
359 | */ |
360 | public function getIterator(): ArrayIterator |
361 | { |
362 | return new ArrayIterator($this->handlers); |
363 | } |
364 | |
365 | /** |
366 | * Set a handler |
367 | * |
368 | * @param string $name |
369 | * @param mixed $value |
370 | * @throws Exception |
371 | * @return void |
372 | */ |
373 | public function __set(string $name, mixed $value): void |
374 | { |
375 | $this->offsetSet($name, $value); |
376 | } |
377 | |
378 | /** |
379 | * Get a handler |
380 | * |
381 | * @param string $name |
382 | * @return mixed |
383 | */ |
384 | public function __get(string $name): mixed |
385 | { |
386 | return $this->offsetGet($name); |
387 | } |
388 | |
389 | /** |
390 | * Is handler set |
391 | * |
392 | * @param string $name |
393 | * @return bool |
394 | */ |
395 | public function __isset(string $name): bool |
396 | { |
397 | return $this->offsetExists($name); |
398 | } |
399 | |
400 | /** |
401 | * Unset a handler |
402 | * |
403 | * @param string $name |
404 | * @return void |
405 | */ |
406 | public function __unset(string $name): void |
407 | { |
408 | $this->offsetUnset($name); |
409 | } |
410 | |
411 | /** |
412 | * ArrayAccess offsetSet |
413 | * |
414 | * @param mixed $offset |
415 | * @param mixed $value |
416 | * @throws Exception |
417 | * @return void |
418 | */ |
419 | public function offsetSet(mixed $offset, mixed $value): void |
420 | { |
421 | if (!($value instanceof HandlerInterface)) { |
422 | throw new Exception('Error: The value passed must be an instance of HandlerInterface'); |
423 | } |
424 | $this->handlers[$offset] = $value; |
425 | } |
426 | |
427 | /** |
428 | * ArrayAccess offsetGet |
429 | * |
430 | * @param mixed $offset |
431 | * @return mixed |
432 | */ |
433 | public function offsetGet(mixed $offset): mixed |
434 | { |
435 | return $this->handlers[$offset] ?? null; |
436 | } |
437 | |
438 | /** |
439 | * ArrayAccess offsetExists |
440 | * |
441 | * @param mixed $offset |
442 | * @return bool |
443 | */ |
444 | public function offsetExists(mixed $offset): bool |
445 | { |
446 | return isset($this->handlers[$offset]); |
447 | } |
448 | |
449 | /** |
450 | * ArrayAccess offsetUnset |
451 | * |
452 | * @param mixed $offset |
453 | * @return void |
454 | */ |
455 | public function offsetUnset(mixed $offset): void |
456 | { |
457 | if (isset($this->handlers[$offset])) { |
458 | unset($this->handlers[$offset]); |
459 | } |
460 | } |
461 | |
462 | /** |
463 | * Render to string |
464 | * |
465 | * @return string |
466 | */ |
467 | public function __toString(): string |
468 | { |
469 | return $this->renderWithHeaders(); |
470 | } |
471 | |
472 | } |