• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

emgiezet / errbitPHP / #89

pending completion
#89

push

php-coveralls

web-flow
Merge pull request #29 from emgiezet/feature/php8.0-support

php8.0 - support removed some fancy stuff like readonly arguments

1 of 1 new or added line in 1 file covered. (100.0%)

337 of 421 relevant lines covered (80.05%)

2.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.26
/src/Errbit/Exception/Notice.php
1
<?php
2
declare(strict_types=1);
3

4
namespace Errbit\Exception;
5

6
use Errbit\Errbit;
7
use Errbit\Utils\XmlBuilder;
8

9
class Notice
10
{
11
    
12
    /**
13
     * @var array
14
     */
15
    private static array $hashArray = [];
16
    
17
    /**
18
     * @var array
19
     */
20
    private array $options;
21
    
22
    /**
23
     * Create a new notice for the given Exception with the given $options.
24
     *
25
     * @param mixed $exception - the exception that occurred
26
     * @param array $options - full configuration + options
27
     */
28
    public function __construct(
29
        private  mixed $exception,
30
        array $options = []
31
    ) {
32
        $this->options = array_merge(
33
            [
34
                'url' => $this->buildRequestUrl(),
35
                'parameters' => !empty($_REQUEST) ? $_REQUEST : [],
36
                'session_data' => !empty($_SESSION) ? $_SESSION : [],
37
                'cgi_data' => !empty($_SERVER) ? $_SERVER : [],
38
            ],
39
            $options
40
        );
41
        
42
        $this->filterData();
43
    }
44
    
45
    /**
46
     * Building request url
4✔
47
     *
4✔
48
     * @return string url
49
     *
4✔
50
     */
4✔
51
    private function buildRequestUrl(): ?string
4✔
52
    {
4✔
53
        if (!empty($_SERVER['REQUEST_URI'])) {
4✔
54
            return sprintf(
55
                '%s://%s%s%s',
4✔
56
                $this->guessProtocol(),
57
                $this->guessHost(),
4✔
58
                $this->guessPort(),
4✔
59
                $_SERVER['REQUEST_URI']
60
            );
61
        }
62
        
63
        return null;
64
    }
65
    
66
    /**
67
     * Protocol guesser
68
     *
69
     * @return string http or https protocol
70
     */
1✔
71
    private function guessProtocol(): string
72
    {
73
        if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
74
            return $_SERVER['HTTP_X_FORWARDED_PROTO'];
75
        } elseif (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
76
            return 'https';
77
        } else {
78
            return 'http';
79
        }
80
    }
81
    
82
    /**
4✔
83
     * Host guesser
4✔
84
     *
85
     * @return string servername
1✔
86
     */
87
    private function guessHost(): string
88
    {
89
        if (!empty($_SERVER['HTTP_HOST'])) {
90
            return $_SERVER['HTTP_HOST'];
91
        } elseif (!empty($_SERVER['SERVER_NAME'])) {
92
            return $_SERVER['SERVER_NAME'];
93
        } else {
94
            return '127.0.0.1';
95
        }
96
    }
97
    
98
    /**
99
     * Port guesser
100
     *
4✔
101
     * @return string port
4✔
102
     */
4✔
103
    private function guessPort(): string
1✔
104
    {
4✔
105
        if (!empty($_SERVER['SERVER_PORT']) && !in_array(
1✔
106
                $_SERVER['SERVER_PORT'],
4✔
107
                [80, 443]
1✔
108
            )) {
4✔
109
            return sprintf(':%d', $_SERVER['SERVER_PORT']);
1✔
110
        }
3✔
111
        
3✔
112
        return '80';
3✔
113
    }
114
    
115
    /**
116
     * Filtering data
117
     *
118
     * @return void
119
     */
120
    private function filterData(): void
121
    {
122
        if (empty($this->options['params_filters'])) {
123
            return;
124
        }
125
        
4✔
126
        foreach (['parameters', 'session_data', 'cgi_data'] as $name) {
4✔
127
            $this->filterParams($name);
4✔
128
        }
129
    }
2✔
130
    
131
    /**
2✔
132
     * Filtering params
2✔
133
     *
4✔
134
     * @param string $name param name
135
     *
136
     * @return void
4✔
137
     */
3✔
138
    private function filterParams(string $name): void
3✔
139
    {
3✔
140
        if (empty($this->options[$name])) {
3✔
141
            return;
3✔
142
        }
3✔
143
        
144
        if (is_array($this->options['params_filters'])) {
3✔
145
            foreach ($this->options['params_filters'] as $pattern) {
3✔
146
                foreach ($this->options[$name] as $key => $value) {
147
                    
3✔
148
                    if (preg_match($pattern, (string)$key)) {
3✔
149
                        $this->options[$name][$key] = '[FILTERED]';
1✔
150
                    }
1✔
151
                }
1✔
152
            }
153
        }
154
    }
1✔
155
    
1✔
156
    // -- Private Methods
157
    
158
    /**
3✔
159
     * Convenience method to instantiate a new notice.
4✔
160
     *
161
     * @param mixed $exception - Exception
4✔
162
     * @param array $options - array of options
4✔
163
     *
4✔
164
     * @return Notice
165
     */
166
    public static function forException(
167
        mixed $exception,
168
        array $options = []
169
    ): Notice {
170
        return new self($exception, $options);
171
    }
172
    
173
    /**
174
     * Build the full XML document for the notice.
175
     *
4✔
176
     * @return string the XML
3✔
177
     */
178
    public function asXml(): string
179
    {
1✔
180
        $exception = $this->exception;
1✔
181
        $options = $this->options;
1✔
182
        $builder = new XmlBuilder();
183
        $self = $this;
1✔
184
        
185
        return $builder->tag(
186
            'notice',
187
            '',
188
            ['version' => Errbit::API_VERSION],
189
            function (XmlBuilder $notice) use ($exception, $options, $self) {
190
                $notice->tag('api-key', $options['api_key']);
191
                $notice->tag(
192
                    'notifier',
193
                    '',
4✔
194
                    [],
4✔
195
                    function (XmlBuilder $notifier) {
4✔
196
                        $notifier->tag('name', Errbit::PROJECT_NAME);
4✔
197
                        $notifier->tag('version', Errbit::VERSION);
198
                        $notifier->tag('url', Errbit::PROJECT_URL);
4✔
199
                    }
4✔
200
                );
4✔
201
                
4✔
202
                $notice->tag(
203
                    'error',
4✔
204
                    '',
4✔
205
                    [],
4✔
206
                    function (XmlBuilder $error) use ($exception, $self) {
4✔
207
                        $class = Notice::className($exception);
4✔
208
                        $error->tag('class', $self->filterTrace($class));
209
                        $error->tag(
4✔
210
                            'message',
4✔
211
                            $self->filterTrace(
4✔
212
                                sprintf(
4✔
213
                                    '%s: %s',
4✔
214
                                    $class,
215
                                    $exception->getMessage()
4✔
216
                                )
4✔
217
                            )
4✔
218
                        );
4✔
219
                        $error->tag(
220
                            'backtrace',
4✔
221
                            '',
4✔
222
                            [],
4✔
223
                            function (XmlBuilder $backtrace) use (
4✔
224
                                $exception,
4✔
225
                                $self
4✔
226
                            ) {
4✔
227
                                $trace = $exception->getTrace();
4✔
228
                                
4✔
229
                                $file1 = $exception->getFile();
4✔
230
                                $backtrace->tag(
231
                                    'line',
4✔
232
                                    '',
233
                                    [
4✔
234
                                        'number' => $exception->getLine(),
4✔
235
                                        'file' => !empty($file1) ? $self->filterTrace(
4✔
236
                                            $file1
4✔
237
                                        ) : '<unknown>',
238
                                        'method' => "<unknown>",
4✔
239
                                    ]
4✔
240
                                );
241
                                
4✔
242
                                // if there is no trace we should add an empty element
4✔
243
                                if (empty($trace)) {
244
                                    $backtrace->tag(
245
                                        'line',
4✔
246
                                        '',
×
247
                                        [
×
248
                                            'number' => '',
×
249
                                            'file' => '',
250
                                            'method' => '',
×
251
                                        ]
×
252
                                    );
253
                                } else {
×
254
                                    foreach ($trace as $frame) {
×
255
                                        $backtrace->tag(
×
256
                                            'line',
4✔
257
                                            '',
4✔
258
                                            [
4✔
259
                                                'number' => $frame['line'] ?? 0,
4✔
260
                                                'file' => isset($frame['file']) ?
261
                                                    $self->filterTrace(
4✔
262
                                                        $frame['file']
4✔
263
                                                    ) : '<unknown>',
4✔
264
                                                'method' => $self->filterTrace(
4✔
265
                                                    $self->formatMethod($frame)
4✔
266
                                                ),
4✔
267
                                            ]
4✔
268
                                        );
269
                                    }
4✔
270
                                }
4✔
271
                            }
4✔
272
                        );
4✔
273
                    }
274
                );
4✔
275
                
4✔
276
                if (!empty($options['url'])
1✔
277
                    || !empty($options['controller'])
1✔
278
                    || !empty($options['action'])
1✔
279
                    || !empty($options['parameters'])
1✔
280
                    || !empty($options['session_data'])
4✔
281
                    || !empty($options['cgi_data'])
4✔
282
                ) {
4✔
283
                    $notice->tag(
4✔
284
                        'request',
4✔
285
                        '',
286
                        [],
4✔
287
                        function (XmlBuilder $request) use ($options) {
4✔
288
                            $request->tag(
4✔
289
                                'url',
4✔
290
                                !empty($options['url']) ? $options['url'] : ''
3✔
291
                            );
3✔
292
                            $request->tag(
3✔
293
                                'component',
3✔
294
                                !empty($options['controller']) ? $options['controller'] : ''
295
                            );
3✔
296
                            $request->tag(
3✔
297
                                'action',
3✔
298
                                !empty($options['action']) ? $options['action'] : ''
3✔
299
                            );
300
                            if (!empty($options['parameters'])) {
4✔
301
                                $request->tag(
3✔
302
                                    'params',
3✔
303
                                    '',
3✔
304
                                    [],
3✔
305
                                    function (XmlBuilder $params) use ($options
306
                                    ) {
3✔
307
                                        Notice::xmlVarsFor(
3✔
308
                                            $params,
3✔
309
                                            $options['parameters']
3✔
310
                                        );
311
                                    }
4✔
312
                                );
4✔
313
                            }
4✔
314
                            
4✔
315
                            if (!empty($options['session_data'])) {
4✔
316
                                $request->tag(
317
                                    'session',
4✔
318
                                    '',
4✔
319
                                    [],
4✔
320
                                    function (XmlBuilder $session) use ($options
4✔
321
                                    ) {
4✔
322
                                        Notice::xmlVarsFor(
4✔
323
                                            $session,
4✔
324
                                            $options['session_data']
325
                                        );
4✔
326
                                    }
×
327
                                );
×
328
                            }
×
329
                            
×
330
                            if (!empty($options['cgi_data'])) {
331
                                $request->tag(
×
332
                                    'cgi-data',
×
333
                                    '',
×
334
                                    [],
×
335
                                    function (XmlBuilder $cgiData) use ($options
336
                                    ) {
4✔
337
                                        Notice::xmlVarsFor(
4✔
338
                                            $cgiData,
4✔
339
                                            $options['cgi_data']
4✔
340
                                        );
341
                                    }
4✔
342
                                );
4✔
343
                            }
4✔
344
                        }
4✔
345
                    );
4✔
346
                }
4✔
347
                
4✔
348
                if (!empty($options['user'])) {
349
                    $notice->tag(
350
                        'user-attributes',
351
                        '',
352
                        [],
353
                        function (XmlBuilder $user) use ($options) {
354
                            Notice::xmlVarsFor($user, $options['user']);
355
                        }
356
                    );
357
                }
358
                
4✔
359
                $notice->tag(
×
360
                    'server-environment',
361
                    '',
362
                    [],
4✔
363
                    function (XmlBuilder $env) use ($options) {
4✔
364
                        $env->tag('project-root', $options['project_root']);
4✔
365
                        $env->tag(
4✔
366
                            'environment-name',
367
                            $options['environment_name']
368
                        );
369
                        //                        $env->tag('hostname', $options['hostname']);
370
                    }
371
                );
372
            }
373
        )->asXml();
374
    }
375
    
4✔
376
    /**
1✔
377
     * Get a human readable class name for the Exception.
378
     *
379
     * Native PHP errors are named accordingly.
4✔
380
     *
4✔
381
     * @param object $exception - the exception object
4✔
382
     *
383
     * @return string - the name to display
4✔
384
     */
×
385
    public static function className(object $exception): string
×
386
    {
4✔
387
        $shortClassname = self::parseClassname($exception::class);
4✔
388
        switch ($shortClassname['classname']) {
4✔
389
            case 'Notice':
4✔
390
                return 'Notice';
391
            case 'Warning':
392
                return 'Warning';
393
            case 'Error':
394
                return 'Error';
395
            case 'Fatal':
396
                return 'Fatal Error';
397
            default:
398
                return $shortClassname['classname'];
399
        }
4✔
400
    }
×
401
    
×
402
    /**
×
403
     * Parses class name to namespace and class name.
×
404
     *
×
405
     * @param string $name Name of class
×
406
     *
×
407
     * @return (string|string[])[]
408
     *
4✔
409
     * @psalm-return array{namespace: list<string>, classname: string}
410
     */
411
    private static function parseClassname(string $name): array
412
    {
413
        return [
414
            'namespace' => array_slice(explode('\\', $name), 0, -1),
415
            'classname' => implode('', array_slice(explode('\\', $name), -1)),
416
        ];
×
417
    }
×
418
    
×
419
    /**
×
420
     * Perform search/replace filters on a backtrace entry.
421
     *
×
422
     * @param string $str the entry from the backtrace
423
     *
424
     * @return string the filtered entry
425
     */
426
    public function filterTrace(string $str): string
427
    {
428
        
429
        if (empty($this->options['backtrace_filters']) || !is_array(
430
                $this->options['backtrace_filters']
431
            )) {
×
432
            return $str;
×
433
        }
×
434
        
×
435
        foreach ($this->options['backtrace_filters'] as $pattern => $replacement) {
436
            $str = preg_replace($pattern, (string)$replacement, $str);
×
437
        }
438
        
439
        return $str;
440
    }
441
    
442
    /**
443
     * Extract a human-readable method/function name from the given stack frame.
444
     *
445
     * @param array $frame - a single entry for the backtrace
446
     *
447
     * @return string -  the name of the method/function
×
448
     */
×
449
    public static function formatMethod(array $frame): string
450
    {
×
451
        if (!empty($frame['class']) && !empty($frame['type']) && !empty($frame['function'])) {
452
            return sprintf(
453
                '%s%s%s()',
454
                $frame['class'],
455
                $frame['type'],
456
                $frame['function']
457
            );
458
        } else {
459
            return sprintf(
460
                '%s()',
4✔
461
                !empty($frame['function']) ? $frame['function'] : '<unknown>'
4✔
462
            );
4✔
463
        }
464
    }
465
    
466
    /**
467
     * Recursively build an list of the all the vars in the given array.
468
     *
469
     * @param \Errbit\Utils\XmlBuilder $builder the builder instance to set the
470
     *     data into
471
     * @param array $array the stack frame entry
472
     *
473
     * @return void
474
     */
475
    public static function xmlVarsFor(XmlBuilder $builder, array $array): void
476
    {
477
        
478
        foreach ($array as $key => $value) {
479
            if (is_object($value)) {
480
                
481
                $hash = spl_object_hash($value);
482
                
483
                $value = (array)$value;
484
            } else {
485
                $hash = null;
486
            }
487
            
488
            if (is_array($value)) {
489
                if (null == $hash || !in_array($hash, self::$hashArray)) {
490
                    self::$hashArray[] = $hash;
491
                    $builder->tag(
492
                        'var',
493
                        '',
494
                        ['key' => $key],
495
                        function ($var) use ($value) {
496
                            Notice::xmlVarsFor($var, $value);
497
                        },
498
                        true
499
                    );
500
                } else {
501
                    $builder->tag(
502
                        'var',
503
                        '*** RECURSION ***',
504
                        ['key' => $key]
505
                    );
506
                }
507
                
508
            } else {
509
                $builder->tag('var', $value, ['key' => $key]);
510
            }
511
        }
512
    }
513
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc