Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.39% covered (success)
92.39%
85 / 92
88.46% covered (success)
88.46%
23 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
PhpHandler
92.39% covered (success)
92.39%
85 / 92
88.46% covered (success)
88.46%
23 / 26
44.85
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
 prepareMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 log
85.71% covered (success)
85.71%
24 / 28
0.00% covered (danger)
0.00%
0 / 1
12.42
 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 (https://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@noladev.com>
7 * @copyright  Copyright (c) 2009-2026 NOLA Interactive, LLC.
8 * @license    https://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@noladev.com>
24 * @copyright  Copyright (c) 2009-2026 NOLA Interactive, LLC.
25 * @license    https://www.popphp.org/license     New BSD License
26 * @version    3.0.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_USER_NOTICE       => 'E_USER_NOTICE',
121        E_USER_WARNING      => 'E_USER_WARNING',
122        E_USER_ERROR        => 'E_USER_ERROR',
123        E_COMPILE_WARNING   => 'E_COMPILE_WARNING',
124        E_COMPILE_ERROR     => 'E_COMPILE_ERROR',
125        E_CORE_WARNING      => 'E_CORE_WARNING',
126        E_CORE_ERROR        => 'E_CORE_ERROR',
127        E_NOTICE            => 'E_NOTICE',
128        E_PARSE             => 'E_PARSE',
129        E_WARNING           => 'E_WARNING',
130        E_ERROR             => 'E_ERROR',
131    ];
132
133    /**
134     * Constructor
135     *
136     * Instantiate a PHP handler object
137     *
138     * @param  ?string $name
139     * @param  ?Logger $logger
140     * @param  array   $loggingParams
141     */
142    public function __construct(?string $name = null, ?Logger $logger = null, array $loggingParams = [])
143    {
144        parent::__construct($name, $logger, $loggingParams);
145
146        $this->parseErrorSettings();
147        $this->parseLimits();
148        $this->parseDisabled();
149
150        $this->dateTime   = ini_get('date.timezone');
151        $this->extensions = array_map('strtolower', get_loaded_extensions());
152
153        sort($this->extensions);
154    }
155
156    /**
157     * Get PHP INI setting
158     *
159     * @param  string $setting
160     * @return mixed
161     */
162    public function getIniSetting(string $setting): mixed
163    {
164        return ini_get($setting);
165    }
166
167    /**
168     * Get full PHP version
169     *
170     * @param  bool $withExtra
171     * @return string
172     */
173    public function getPhpVersion(bool $withExtra = true): string
174    {
175        return (!$withExtra) ? str_replace($this->extraVersion, '', $this->version) : $this->version;
176    }
177
178    /**
179     * Get PHP major version
180     *
181     * @return int
182     */
183    public function getPhpMajorVersion(): int
184    {
185        return $this->majorVersion;
186    }
187
188    /**
189     * Get PHP minor version
190     *
191     * @return int
192     */
193    public function getPhpMinorVersion(): int
194    {
195        return $this->minorVersion;
196    }
197
198    /**
199     * Get PHP release version
200     *
201     * @return int
202     */
203    public function getPhpReleaseVersion(): int
204    {
205        return $this->releaseVersion;
206    }
207
208    /**
209     * Get PHP extra version
210     *
211     * @return string
212     */
213    public function getPhpExtraVersion(): string
214    {
215        return $this->extraVersion;
216    }
217
218    /**
219     * Get date/time
220     *
221     * @return ?string
222     */
223    public function getDateTime(): ?string
224    {
225        return $this->dateTime;
226    }
227
228    /**
229     * Get error settings
230     *
231     * @return array
232     */
233    public function getErrorSettings(): array
234    {
235        return $this->errorSettings;
236    }
237
238    /**
239     * Get error setting
240     *
241     * @param  string $setting
242     * @return mixed
243     */
244    public function getErrorSetting(string $setting): mixed
245    {
246        return $this->errorSettings[$setting] ?? null;
247    }
248
249    /**
250     * Get human-readable error reporting list
251     *
252     * @return array
253     */
254    public function getErrorReportingList(): array
255    {
256        return $this->errorSettings['error_reporting_list'];
257    }
258
259    /**
260     * Has error level
261     *
262     * @param  int $level
263     * @return bool
264     */
265    public function hasErrorLevel(int $level): bool
266    {
267        return in_array($this->errorReporting[$level], $this->errorSettings['error_reporting_list']);
268    }
269
270    /**
271     * Get limits
272     *
273     * @return array
274     */
275    public function getLimits(): array
276    {
277        return $this->limits;
278    }
279
280    /**
281     * Get limit
282     *
283     * @param  string $limit
284     * @return mixed
285     */
286    public function getLimit(string $limit): mixed
287    {
288        return $this->limits[$limit] ?? null;
289    }
290
291    /**
292     * Get extensions
293     *
294     * @return array
295     */
296    public function getExtensions(): array
297    {
298        return $this->extensions;
299    }
300
301    /**
302     * Has extensions
303     *
304     * @param  string $extension
305     * @return bool
306     */
307    public function hasExtension(string $extension): bool
308    {
309        return in_array(strtolower($extension), $this->extensions);
310    }
311
312    /**
313     * Get disabled functions
314     *
315     * @return array
316     */
317    public function getDisabledFunctions(): array
318    {
319        return $this->disabledFunctions;
320    }
321
322    /**
323     * Has disabled function
324     *
325     * @param  string $function
326     * @return bool
327     */
328    public function hasDisabledFunction(string $function): bool
329    {
330        return in_array($function, $this->disabledFunctions);
331    }
332
333    /**
334     * Get disabled classes
335     *
336     * @return array
337     */
338    public function getDisabledClasses(): array
339    {
340        return $this->disabledClasses;
341    }
342
343    /**
344     * Has disabled class
345     *
346     * @param  string $class
347     * @return bool
348     */
349    public function hasDisabledClass(string $class): bool
350    {
351        return in_array($class, $this->disabledClasses);
352    }
353
354    /**
355     * Prepare handler data for storage
356     *
357     * @return array
358     */
359    public function prepare(): array
360    {
361        return [
362            'php_version'         => $this->version,
363            'php_major_version'   => $this->majorVersion,
364            'php_minor_version'   => $this->minorVersion,
365            'php_release_version' => $this->releaseVersion,
366            'php_extra_version'   => $this->extraVersion,
367            'data_time'           => $this->dateTime,
368            'error_settings'      => $this->errorSettings,
369            'limits'              => $this->limits,
370            'extensions'          => $this->extensions,
371            'disabled_functions'  => $this->disabledFunctions,
372            'disabled_classes'    => $this->disabledClasses,
373        ];
374    }
375
376    /**
377     * Prepare handler message
378     *
379     * @param  ?array $context
380     * @return string
381     */
382    public function prepareMessage(?array $context = null): string
383    {
384        return 'PHP info has been logged.';
385    }
386
387    /**
388     * Trigger handler logging
389     *
390     * @throws Exception
391     * @return void
392     */
393    public function log(): void
394    {
395        if (($this->hasLogger()) && ($this->hasLoggingParams())) {
396            $logLevel     = $this->loggingParams['level'] ?? null;
397            $versionLimit = $this->loggingParams['version'] ?? null;
398            $extensions   = $this->loggingParams['extensions'] ?? null;
399
400            if ($logLevel !== null) {
401                $context = $this->prepare();
402
403                if (!empty($versionLimit) || !empty($extensions)) {
404                    if (!empty($versionLimit)) {
405                        if (version_compare($this->getPhpVersion(false), $versionLimit) == -1) {
406                            $this->logger->log($logLevel, 'The current version of PHP ' . $this->getPhpVersion(false) .
407                                ' is less than the required version ' . $versionLimit . '.',
408                                $context
409                            );
410                        }
411                    }
412                    if (!empty($extensions)) {
413                        if (is_string($extensions)) {
414                            if (str_contains($extensions, ',')) {
415                                $extensions = array_map(function ($value) {
416                                    return strtolower(trim($value));
417                                }, explode(',', $extensions));
418                            } else {
419                                $extensions = [strtolower($extensions)];
420                            }
421                        }
422                        $extensionDiff = array_diff($extensions, $this->extensions);
423                        if (!empty($extensionDiff)) {
424                            $this->logger->log($logLevel, 'The current of PHP extensions are required but not active: ' .
425                                implode(', ', $extensionDiff),
426                                $context
427                            );
428                        }
429                    }
430                } else {
431                    $this->logger->log($logLevel, $this->prepareMessage(), $context);
432                }
433            } else {
434                throw new Exception('Error: The log level parameter was not set.');
435            }
436        }
437    }
438
439    /**
440     * Get PHP error settings
441     *
442     * @return void
443     */
444    public function parseErrorSettings(): void
445    {
446        $this->errorSettings['error_reporting']        = ini_get('error_reporting');
447        $this->errorSettings['display_errors']         = !empty(ini_get('display_errors'));
448        $this->errorSettings['display_startup_errors'] = !empty(ini_get('display_startup_errors'));
449        $this->errorSettings['log_errors']             = !empty(ini_get('log_errors'));
450
451        if (!empty($this->errorSettings['error_reporting'])) {
452            if ($this->errorSettings['error_reporting'] == E_ALL) {
453                $this->errorSettings['error_reporting_list'][] = $this->errorReporting[E_ALL];
454            } else {
455                foreach($this->errorReporting as $errorNumber => $errorName) {
456                    if (($this->errorSettings['error_reporting'] & $errorNumber) == $errorNumber) {
457                        $this->errorSettings['error_reporting_list'][] = $errorName;
458                    }
459                }
460            }
461        }
462    }
463
464    /**
465     * Get PHP limits
466     *
467     * @return void
468     */
469    public function parseLimits(): void
470    {
471        $this->limits['max_execution_time']      = ini_get('max_execution_time');
472        $this->limits['max_input_time']          = ini_get('max_input_time');
473        $this->limits['max_input_nesting_level'] = ini_get('max_input_nesting_level');
474        $this->limits['max_input_vars']          = ini_get('max_input_vars');
475        $this->limits['post_max_size']           = ini_get('post_max_size');
476        $this->limits['file_uploads']            = ini_get('file_uploads');
477        $this->limits['upload_max_filesize']     = ini_get('upload_max_filesize');
478        $this->limits['max_file_uploads']        = ini_get('max_file_uploads');
479    }
480
481    /**
482     * Get PHP disabled functions and classes
483     *
484     * @return void
485     */
486    public function parseDisabled(): void
487    {
488        $disabledFunctions = ini_get('disable_functions');
489        $disabledClasses   = ini_get('disable_classes');
490
491        if (!empty($disabledFunctions)) {
492            $this->disabledFunctions = array_map('trim', explode(',', $disabledFunctions));
493        }
494        if (!empty($disabledClasses)) {
495            $this->disabledClasses = array_map('trim', explode(',', $disabledClasses));
496        }
497    }
498
499}