Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.83% covered (success)
97.83%
90 / 92
96.15% covered (success)
96.15%
25 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
PhpHandler
97.83% covered (success)
97.83%
90 / 92
96.15% covered (success)
96.15%
25 / 26
42
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
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
10
 parseErrorSettings
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 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
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Debug\Handler;
16
17use Psr\Log\LoggerInterface;
18
19/**
20 * Debug time handler class
21 *
22 * @category   Pop
23 * @package    Pop\Debug
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    4.0.0
28 */
29class PhpHandler extends AbstractHandler
30{
31
32    /**
33     * PHP version
34     * @var string
35     */
36    protected string $version = PHP_VERSION;
37    /**
38     * PHP major version
39     * @var int
40     */
41    protected int $majorVersion = PHP_MAJOR_VERSION;
42
43    /**
44     * PHP minor version
45     * @var int
46     */
47    protected int $minorVersion = PHP_MINOR_VERSION;
48
49    /**
50     * PHP release version
51     * @var int
52     */
53    protected int $releaseVersion = PHP_RELEASE_VERSION;
54
55    /**
56     * PHP version extra
57     * @var string
58     */
59    protected string $extraVersion = PHP_EXTRA_VERSION;
60
61    /**
62     * PHP date/time
63     * @var ?string
64     */
65    protected ?string $dateTime = null;
66
67    /**
68     * PHP error settings
69     * @var array
70     */
71    protected array $errorSettings = [
72        'error_reporting_list'   => [],
73        'error_reporting'        => null,
74        'display_errors'         => null,
75        'display_startup_errors' => null,
76        'log_errors'             => null,
77    ];
78
79    /**
80     * PHP limits
81     * @var array
82     */
83    protected array $limits = [
84        'max_execution_time'      => null,
85        'max_input_time'          => null,
86        'max_input_nesting_level' => null,
87        'max_input_vars'          => null,
88        'post_max_size'           => null,
89        'file_uploads'            => null,
90        'upload_max_filesize'     => null,
91        'max_file_uploads'        => null,
92    ];
93
94    /**
95     * PHP extensions
96     * @var array
97     */
98    protected array $extensions = [];
99
100    /**
101     * PHP disabled classes
102     * @var array
103     */
104    protected array $disabledFunctions = [];
105
106    /**
107     * PHP disabled classes
108     * @var array
109     */
110    protected array $disabledClasses = [];
111
112    /**
113     * PHP error reporting constants
114     * @var array
115     */
116    protected array $errorReporting = [
117        E_ALL               => 'E_ALL',
118        E_USER_DEPRECATED   => 'E_USER_DEPRECATED',
119        E_DEPRECATED        => 'E_DEPRECATED',
120        E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
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  ?LoggerInterface $logger
141     * @param  array            $loggingParams
142     */
143    public function __construct(?string $name = null, ?LoggerInterface $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 handler message
379     *
380     * @param  ?array $context
381     * @return string
382     */
383    public function prepareMessage(?array $context = null): string
384    {
385        return 'PHP info has been logged.';
386    }
387
388    /**
389     * Trigger handler logging
390     *
391     * @throws Exception
392     * @return void
393     */
394    public function log(): void
395    {
396        $logLevel = $this->resolveLogLevel();
397        if ($logLevel === null) {
398            return;
399        }
400
401        $versionLimit = $this->loggingParams['version'] ?? null;
402        $extensions   = $this->loggingParams['extensions'] ?? null;
403        $context      = $this->prepare();
404
405        if (!empty($versionLimit) || !empty($extensions)) {
406            if (!empty($versionLimit)) {
407                if (version_compare($this->getPhpVersion(false), $versionLimit) == -1) {
408                    $this->logger->log($logLevel, 'The current version of PHP ' . $this->getPhpVersion(false) .
409                        ' is less than the required version ' . $versionLimit . '.',
410                        $context
411                    );
412                }
413            }
414            if (!empty($extensions)) {
415                if (is_string($extensions)) {
416                    if (str_contains($extensions, ',')) {
417                        $extensions = array_map(function ($value) {
418                            return strtolower(trim($value));
419                        }, explode(',', $extensions));
420                    } else {
421                        $extensions = [strtolower($extensions)];
422                    }
423                }
424                $extensionDiff = array_diff($extensions, $this->extensions);
425                if (!empty($extensionDiff)) {
426                    $this->logger->log($logLevel, 'The current of PHP extensions are required but not active: ' .
427                        implode(', ', $extensionDiff),
428                        $context
429                    );
430                }
431            }
432        } else {
433            $this->logger->log($logLevel, $this->prepareMessage(), $context);
434        }
435    }
436
437    /**
438     * Get PHP error settings
439     *
440     * @return void
441     */
442    public function parseErrorSettings(): void
443    {
444        $this->errorSettings['error_reporting']        = ini_get('error_reporting');
445        $this->errorSettings['display_errors']         = !empty(ini_get('display_errors'));
446        $this->errorSettings['display_startup_errors'] = !empty(ini_get('display_startup_errors'));
447        $this->errorSettings['log_errors']             = !empty(ini_get('log_errors'));
448
449        if (!empty($this->errorSettings['error_reporting'])) {
450            if ($this->errorSettings['error_reporting'] == E_ALL) {
451                $this->errorSettings['error_reporting_list'][] = $this->errorReporting[E_ALL];
452            } else {
453                $errorReportingValue = (int)$this->errorSettings['error_reporting'];
454                foreach($this->errorReporting as $errorNumber => $errorName) {
455                    if (($errorReportingValue & (int)$errorNumber) == $errorNumber) {
456                        $this->errorSettings['error_reporting_list'][] = $errorName;
457                    }
458                }
459            }
460        }
461    }
462
463    /**
464     * Get PHP limits
465     *
466     * @return void
467     */
468    public function parseLimits(): void
469    {
470        $this->limits['max_execution_time']      = ini_get('max_execution_time');
471        $this->limits['max_input_time']          = ini_get('max_input_time');
472        $this->limits['max_input_nesting_level'] = ini_get('max_input_nesting_level');
473        $this->limits['max_input_vars']          = ini_get('max_input_vars');
474        $this->limits['post_max_size']           = ini_get('post_max_size');
475        $this->limits['file_uploads']            = ini_get('file_uploads');
476        $this->limits['upload_max_filesize']     = ini_get('upload_max_filesize');
477        $this->limits['max_file_uploads']        = ini_get('max_file_uploads');
478    }
479
480    /**
481     * Get PHP disabled functions and classes
482     *
483     * @return void
484     */
485    public function parseDisabled(): void
486    {
487        $disabledFunctions = ini_get('disable_functions');
488        $disabledClasses   = ini_get('disable_classes');
489
490        if (!empty($disabledFunctions)) {
491            $this->disabledFunctions = array_map('trim', explode(',', $disabledFunctions));
492        }
493        if (!empty($disabledClasses)) {
494            $this->disabledClasses = array_map('trim', explode(',', $disabledClasses));
495        }
496    }
497
498}