Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.94% covered (success)
94.94%
75 / 79
93.75% covered (success)
93.75%
30 / 32
CRAP
0.00% covered (danger)
0.00%
0 / 1
Debugger
94.94% covered (success)
94.94%
75 / 79
93.75% covered (success)
93.75%
30 / 32
56.41
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
8
 addHandlers
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addHandler
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 hasHandler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHandler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHandlers
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setStorage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasStorage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStorage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addLogger
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getData
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getById
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getByType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 has
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clear
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 save
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 render
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getRequestId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 renderWithHeaders
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 generateId
40.00% covered (warning)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
4.94
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetSet
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 offsetGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
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 */
14namespace Pop\Debug;
15
16use Pop\Debug\Handler\HandlerInterface;
17use Pop\Debug\Storage\StorageInterface;
18use Pop\Log\Logger;
19use 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 */
31class 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}