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