Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.62% covered (success)
97.62%
123 / 126
92.59% covered (success)
92.59%
25 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
PhpHandler
97.62% covered (success)
97.62%
123 / 126
92.59% covered (success)
92.59%
25 / 27
55
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getIniSetting
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPhpVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getPhpMajorVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPhpMinorVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPhpReleaseVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPhpExtraVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDateTime
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getErrorSettings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getErrorSetting
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getErrorReportingList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasErrorLevel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLimits
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLimit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtensions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasExtension
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisabledFunctions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDisabledFunction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisabledClasses
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDisabledClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 prepareHeaderAsString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 prepareAsString
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
7
 log
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
15
 parseErrorSettings
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
5.03
 parseLimits
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 parseDisabled
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
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\Handler;
15
16use Pop\Log\Logger;
17
18/**
19 * Debug time handler class
20 *
21 * @category   Pop
22 * @package    Pop\Debug
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    2.2.0
27 */
28class PhpHandler extends AbstractHandler
29{
30
31    /**
32     * PHP version
33     * @var string
34     */
35    protected string $version = PHP_VERSION;
36    /**
37     * PHP major version
38     * @var int
39     */
40    protected int $majorVersion = PHP_MAJOR_VERSION;
41
42    /**
43     * PHP minor version
44     * @var int
45     */
46    protected int $minorVersion = PHP_MINOR_VERSION;
47
48    /**
49     * PHP release version
50     * @var int
51     */
52    protected int $releaseVersion = PHP_RELEASE_VERSION;
53
54    /**
55     * PHP version extra
56     * @var string
57     */
58    protected string $extraVersion = PHP_EXTRA_VERSION;
59
60    /**
61     * PHP date/time
62     * @var ?string
63     */
64    protected ?string $dateTime = null;
65
66    /**
67     * PHP error settings
68     * @var array
69     */
70    protected array $errorSettings = [
71        'error_reporting_list'   => [],
72        'error_reporting'        => null,
73        'display_errors'         => null,
74        'display_startup_errors' => null,
75        'log_errors'             => null,
76    ];
77
78    /**
79     * PHP limits
80     * @var array
81     */
82    protected array $limits = [
83        'max_execution_time'      => null,
84        'max_input_time'          => null,
85        'max_input_nesting_level' => null,
86        'max_input_vars'          => null,
87        'post_max_size'           => null,
88        'file_uploads'            => null,
89        'upload_max_filesize'     => null,
90        'max_file_uploads'        => null,
91    ];
92
93    /**
94     * PHP extensions
95     * @var array
96     */
97    protected array $extensions = [];
98
99    /**
100     * PHP disabled classes
101     * @var array
102     */
103    protected array $disabledFunctions = [];
104
105    /**
106     * PHP disabled classes
107     * @var array
108     */
109    protected array $disabledClasses = [];
110
111    /**
112     * PHP error reporting constants
113     * @var array
114     */
115    protected array $errorReporting = [
116        E_ALL               => 'E_ALL',
117        E_USER_DEPRECATED   => 'E_USER_DEPRECATED',
118        E_DEPRECATED        => 'E_DEPRECATED',
119        E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
120        E_STRICT            => 'E_STRICT',
121        E_USER_NOTICE       => 'E_USER_NOTICE',
122        E_USER_WARNING      => 'E_USER_WARNING',
123        E_USER_ERROR        => 'E_USER_ERROR',
124        E_COMPILE_WARNING   => 'E_COMPILE_WARNING',
125        E_COMPILE_ERROR     => 'E_COMPILE_ERROR',
126        E_CORE_WARNING      => 'E_CORE_WARNING',
127        E_CORE_ERROR        => 'E_CORE_ERROR',
128        E_NOTICE            => 'E_NOTICE',
129        E_PARSE             => 'E_PARSE',
130        E_WARNING           => 'E_WARNING',
131        E_ERROR             => 'E_ERROR',
132    ];
133
134    /**
135     * Constructor
136     *
137     * Instantiate a PHP handler object
138     *
139     * @param  ?string $name
140     * @param  ?Logger $logger
141     * @param  array   $loggingParams
142     */
143    public function __construct(?string $name = null, ?Logger $logger = null, array $loggingParams = [])
144    {
145        parent::__construct($name, $logger, $loggingParams);
146
147        $this->parseErrorSettings();
148        $this->parseLimits();
149        $this->parseDisabled();
150
151        $this->dateTime   = ini_get('date.timezone');
152        $this->extensions = array_map('strtolower', get_loaded_extensions());
153
154        sort($this->extensions);
155    }
156
157    /**
158     * Get PHP INI setting
159     *
160     * @param  string $setting
161     * @return mixed
162     */
163    public function getIniSetting(string $setting): mixed
164    {
165        return ini_get($setting);
166    }
167
168    /**
169     * Get full PHP version
170     *
171     * @param  bool $withExtra
172     * @return string
173     */
174    public function getPhpVersion(bool $withExtra = true): string
175    {
176        return (!$withExtra) ? str_replace($this->extraVersion, '', $this->version) : $this->version;
177    }
178
179    /**
180     * Get PHP major version
181     *
182     * @return int
183     */
184    public function getPhpMajorVersion(): int
185    {
186        return $this->majorVersion;
187    }
188
189    /**
190     * Get PHP minor version
191     *
192     * @return int
193     */
194    public function getPhpMinorVersion(): int
195    {
196        return $this->minorVersion;
197    }
198
199    /**
200     * Get PHP release version
201     *
202     * @return int
203     */
204    public function getPhpReleaseVersion(): int
205    {
206        return $this->releaseVersion;
207    }
208
209    /**
210     * Get PHP extra version
211     *
212     * @return string
213     */
214    public function getPhpExtraVersion(): string
215    {
216        return $this->extraVersion;
217    }
218
219    /**
220     * Get date/time
221     *
222     * @return ?string
223     */
224    public function getDateTime(): ?string
225    {
226        return $this->dateTime;
227    }
228
229    /**
230     * Get error settings
231     *
232     * @return array
233     */
234    public function getErrorSettings(): array
235    {
236        return $this->errorSettings;
237    }
238
239    /**
240     * Get error setting
241     *
242     * @param  string $setting
243     * @return mixed
244     */
245    public function getErrorSetting(string $setting): mixed
246    {
247        return $this->errorSettings[$setting] ?? null;
248    }
249
250    /**
251     * Get human-readable error reporting list
252     *
253     * @return array
254     */
255    public function getErrorReportingList(): array
256    {
257        return $this->errorSettings['error_reporting_list'];
258    }
259
260    /**
261     * Has error level
262     *
263     * @param  int $level
264     * @return bool
265     */
266    public function hasErrorLevel(int $level): bool
267    {
268        return in_array($this->errorReporting[$level], $this->errorSettings['error_reporting_list']);
269    }
270
271    /**
272     * Get limits
273     *
274     * @return array
275     */
276    public function getLimits(): array
277    {
278        return $this->limits;
279    }
280
281    /**
282     * Get limit
283     *
284     * @param  string $limit
285     * @return mixed
286     */
287    public function getLimit(string $limit): mixed
288    {
289        return $this->limits[$limit] ?? null;
290    }
291
292    /**
293     * Get extensions
294     *
295     * @return array
296     */
297    public function getExtensions(): array
298    {
299        return $this->extensions;
300    }
301
302    /**
303     * Has extensions
304     *
305     * @param  string $extension
306     * @return bool
307     */
308    public function hasExtension(string $extension): bool
309    {
310        return in_array(strtolower($extension), $this->extensions);
311    }
312
313    /**
314     * Get disabled functions
315     *
316     * @return array
317     */
318    public function getDisabledFunctions(): array
319    {
320        return $this->disabledFunctions;
321    }
322
323    /**
324     * Has disabled function
325     *
326     * @param  string $function
327     * @return bool
328     */
329    public function hasDisabledFunction(string $function): bool
330    {
331        return in_array($function, $this->disabledFunctions);
332    }
333
334    /**
335     * Get disabled classes
336     *
337     * @return array
338     */
339    public function getDisabledClasses(): array
340    {
341        return $this->disabledClasses;
342    }
343
344    /**
345     * Has disabled class
346     *
347     * @param  string $class
348     * @return bool
349     */
350    public function hasDisabledClass(string $class): bool
351    {
352        return in_array($class, $this->disabledClasses);
353    }
354
355    /**
356     * Prepare handler data for storage
357     *
358     * @return array
359     */
360    public function prepare(): array
361    {
362        return [
363            'php_version'         => $this->version,
364            'php_major_version'   => $this->majorVersion,
365            'php_minor_version'   => $this->minorVersion,
366            'php_release_version' => $this->releaseVersion,
367            'php_extra_version'   => $this->extraVersion,
368            'data_time'           => $this->dateTime,
369            'error_settings'      => $this->errorSettings,
370            'limits'              => $this->limits,
371            'extensions'          => $this->extensions,
372            'disabled_functions'  => $this->disabledFunctions,
373            'disabled_classes'    => $this->disabledClasses,
374        ];
375    }
376
377    /**
378     * Prepare header string
379     *
380     * @return string
381     */
382    public function prepareHeaderAsString(): string
383    {
384        $string  = ((!empty($this->name)) ? $this->name . ' ' : '') . 'PHP Handler';
385        $string .= PHP_EOL . str_repeat('=', strlen($string)) . PHP_EOL;
386
387        return $string;
388    }
389
390    /**
391     * Prepare handler data as string
392     *
393     * @return string
394     */
395    public function prepareAsString(): string
396    {
397        $string  = 'PHP Version: ' . $this->version . PHP_EOL . str_repeat('-', strlen($this->version) + 13) . PHP_EOL;
398        $string .= ' - Major Version: ' . $this->majorVersion . PHP_EOL;
399        $string .= ' - Minor Version: ' . $this->minorVersion . PHP_EOL;
400        $string .= ' - Release Version: ' . $this->releaseVersion . PHP_EOL;
401        $string .= ' - Extra Version: ' . $this->extraVersion . PHP_EOL . PHP_EOL;
402
403        $string .= 'Date/Time:' . PHP_EOL . str_repeat('-', 10) . PHP_EOL;
404        $string .= ' - ' . $this->dateTime . PHP_EOL . PHP_EOL;
405
406        $string .= 'Error Settings:' . PHP_EOL . str_repeat('-', 15) . PHP_EOL;
407        $string .= ' - Display Errors: ' . (($this->errorSettings['display_errors']) ? 'On' : 'Off') . PHP_EOL;
408        $string .= ' - Display Startup Errors: ' . (($this->errorSettings['display_startup_errors']) ? 'On' : 'Off') . PHP_EOL;
409        $string .= ' - Log Errors: ' . (($this->errorSettings['log_errors']) ? 'On' : 'Off') . PHP_EOL;
410        $string .= ' - Error Reporting: ' . $this->errorSettings['error_reporting'] . PHP_EOL;
411        $string .= ' - Error Reporting List: ' . implode(', ', $this->errorSettings['error_reporting_list']) . PHP_EOL . PHP_EOL;
412
413        $string .= 'Limits:' . PHP_EOL . str_repeat('-', 7) . PHP_EOL;
414        $string .= ' - Max Execution Time: ' . $this->limits['max_execution_time'] . PHP_EOL;
415        $string .= ' - Max Input Time: ' . $this->limits['max_input_time'] . PHP_EOL;
416        $string .= ' - Max Input Nesting Level: ' . $this->limits['max_input_nesting_level'] . PHP_EOL;
417        $string .= ' - Max Input Vars: ' . $this->limits['max_input_vars'] . PHP_EOL;
418        $string .= ' - Post Max Size: ' . $this->limits['post_max_size'] . PHP_EOL;
419        $string .= ' - File Uploads: ' . (($this->limits['file_uploads']) ? 'On' : 'Off') . PHP_EOL;
420        $string .= ' - Upload Max Filesize: ' . $this->limits['upload_max_filesize'] . PHP_EOL;
421        $string .= ' - Max File Uploads: ' . $this->limits['max_file_uploads'] . PHP_EOL . PHP_EOL;
422
423        $string .= 'Disabled Functions:' . PHP_EOL . str_repeat('-', 19) . PHP_EOL;
424        $string .= ' - ' . (!empty($this->disabledFunctions) ? implode(', ', $this->disabledFunctions) : '(None)') . PHP_EOL . PHP_EOL;
425
426        $string .= 'Disabled Classes:' . PHP_EOL . str_repeat('-', 17) . PHP_EOL;
427        $string .= ' - ' . (!empty($this->disabledClasses) ? implode(', ', $this->disabledClasses) : '(None)') . PHP_EOL . PHP_EOL;
428
429        return $string;
430    }
431
432    /**
433     * Trigger handler logging
434     *
435     * @throws Exception
436     * @return void
437     */
438    public function log(): void
439    {
440        if (($this->hasLogger()) && ($this->hasLoggingParams())) {
441            $logLevel     = $this->loggingParams['level'] ?? null;
442            $versionLimit = $this->loggingParams['version'] ?? null;
443            $extensions   = $this->loggingParams['extensions'] ?? null;
444            $useContext   = $this->loggingParams['context'] ?? null;
445
446            if ($logLevel !== null) {
447                $context = [];
448
449                if (!empty($versionLimit) || !empty($extensions)) {
450                    if (!empty($versionLimit)) {
451                        if (version_compare($this->getPhpVersion(false), $versionLimit) == -1) {
452                            $this->logger->log($logLevel, 'The current version of PHP ' . $this->getPhpVersion(false) .
453                                ' is less than the required version ' . $versionLimit . '.',
454                                $context
455                            );
456                        }
457                    }
458                    if (!empty($extensions)) {
459                        if (is_string($extensions)) {
460                            if (str_contains($extensions, ',')) {
461                                $extensions = array_map(function ($value) {
462                                    return strtolower(trim($value));
463                                }, explode(',', $extensions));
464                            } else {
465                                $extensions = [strtolower($extensions)];
466                            }
467                        }
468                        $extensionDiff = array_diff($extensions, $this->extensions);
469                        if (!empty($extensionDiff)) {
470                            $this->logger->log($logLevel, 'The current of PHP extensions are required but not active: ' .
471                                implode(', ', $extensionDiff),
472                                $context
473                            );
474                        }
475                    }
476                } else {
477                    $context['php_info'] = (($useContext !== null) && (strtolower($useContext) == 'text')) ?
478                        $this->prepareAsString() : $this->prepare();
479
480                    if (is_string($useContext)) {
481                        $context['format'] = $useContext;
482                    }
483
484                    $this->logger->log($logLevel, "PHP info has been logged", $context);
485                }
486            } else {
487                throw new Exception('Error: The log level parameter was not set.');
488            }
489        }
490    }
491
492    /**
493     * Get PHP error settings
494     *
495     * @return void
496     */
497    public function parseErrorSettings(): void
498    {
499        $this->errorSettings['error_reporting']        = ini_get('error_reporting');
500        $this->errorSettings['display_errors']         = !empty(ini_get('display_errors'));
501        $this->errorSettings['display_startup_errors'] = !empty(ini_get('display_startup_errors'));
502        $this->errorSettings['log_errors']             = !empty(ini_get('log_errors'));
503
504        if (!empty($this->errorSettings['error_reporting'])) {
505            if ($this->errorSettings['error_reporting'] == E_ALL) {
506                $this->errorSettings['error_reporting_list'][] = $this->errorReporting[E_ALL];
507            } else {
508                foreach($this->errorReporting as $errorNumber => $errorName) {
509                    if (($this->errorSettings['error_reporting'] & $errorNumber) == $errorNumber) {
510                        $this->errorSettings['error_reporting_list'][] = $errorName;
511                    }
512                }
513            }
514        }
515    }
516
517    /**
518     * Get PHP limits
519     *
520     * @return void
521     */
522    public function parseLimits(): void
523    {
524        $this->limits['max_execution_time']      = ini_get('max_execution_time');
525        $this->limits['max_input_time']          = ini_get('max_input_time');
526        $this->limits['max_input_nesting_level'] = ini_get('max_input_nesting_level');
527        $this->limits['max_input_vars']          = ini_get('max_input_vars');
528        $this->limits['post_max_size']           = ini_get('post_max_size');
529        $this->limits['file_uploads']            = ini_get('file_uploads');
530        $this->limits['upload_max_filesize']     = ini_get('upload_max_filesize');
531        $this->limits['max_file_uploads']        = ini_get('max_file_uploads');
532    }
533
534    /**
535     * Get PHP disabled functions and classes
536     *
537     * @return void
538     */
539    public function parseDisabled(): void
540    {
541        $disabledFunctions = ini_get('disable_functions');
542        $disabledClasses   = ini_get('disable_classes');
543
544        if (!empty($disabledFunctions)) {
545            $this->disabledFunctions = array_map('trim', explode(',', $disabledFunctions));
546        }
547        if (!empty($disabledClasses)) {
548            $this->disabledClasses = array_map('trim', explode(',', $disabledClasses));
549        }
550    }
551
552}