Passed
Pull Request — 1.x (#334)
by Akihito
12:50 queued 10:32
created

ErrorContext::__construct()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 22
nc 20
nop 3
dl 0
loc 44
rs 8.4444
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource\SemanticLog\Profile\Verbose;
6
7
use JsonSerializable;
8
use Koriym\SemanticLogger\AbstractContext;
9
use Override;
10
use Throwable;
11
12
use function crc32;
13
use function dechex;
14
use function file_exists;
15
use function file_put_contents;
16
use function function_exists;
17
use function is_string;
18
use function serialize;
19
use function sprintf;
20
use function str_replace;
21
use function sys_get_temp_dir;
22
use function uniqid;
23
use function xdebug_stop_trace;
24
use function xhprof_disable;
25
26
final class ErrorContext extends AbstractContext implements JsonSerializable
27
{
28
    /** @psalm-suppress InvalidClassConstantType */
29
    public const TYPE = 'bear_resource_error';
30
31
    /** @psalm-suppress InvalidClassConstantType */
32
    public const SCHEMA_URL = 'https://bearsunday.github.io/BEAR.Resource/schemas/error-context.json';
33
34
    public readonly string $exceptionId;
35
    public readonly string $exceptionAsString;
36
    public ?string $xhprofFile;
37
    public ?string $xdebugTraceFile;
38
39
    public function __construct(
40
        Throwable $exception,
41
        string $exceptionId = '',
42
        ?OpenContext $openContext = null,
43
    ) {
44
        $this->exceptionAsString = (string) $exception;
0 ignored issues
show
Bug introduced by
The property exceptionAsString is declared read-only in BEAR\Resource\SemanticLo...le\Verbose\ErrorContext.
Loading history...
45
        $this->exceptionId = $exceptionId !== '' ? $exceptionId : $this->createExceptionId();
0 ignored issues
show
Bug introduced by
The property exceptionId is declared read-only in BEAR\Resource\SemanticLo...le\Verbose\ErrorContext.
Loading history...
46
47
        // Initialize profiling files
48
        $this->xhprofFile = null;
49
        $this->xdebugTraceFile = null;
50
51
        if ($openContext === null) {
52
            return;
53
        }
54
55
        // Stop profiling and save files
56
        if (function_exists('xhprof_disable')) {
57
            $xhprofData = xhprof_disable();
58
            $filename = sprintf(
59
                '%s/xhprof_%s_%s.xhprof',
60
                sys_get_temp_dir(),
61
                str_replace(['/', ':', '?'], '_', $openContext->uri),
62
                uniqid('', true),
63
            );
64
65
            if (file_put_contents($filename, serialize($xhprofData)) !== false) {
66
                $this->xhprofFile = $filename;
67
            }
68
        }
69
70
        // Handle Xdebug trace
71
        $xdebugId = $openContext->getXdebugId();
72
        if ($xdebugId === null) {
73
            return; // @codeCoverageIgnore
74
        }
75
76
        /** @var string|null $traceFile */
77
        $traceFile = @xdebug_stop_trace(); // @phpstan-ignore-line
78
        if (! is_string($traceFile) || ! file_exists($traceFile)) {
0 ignored issues
show
introduced by
The condition is_string($traceFile) is always true.
Loading history...
79
            return;
80
        }
81
82
        $this->xdebugTraceFile = $traceFile; // @codeCoverageIgnore
83
    }
84
85
    public static function create(
86
        Throwable $exception,
87
        string $exceptionId = '',
88
        ?OpenContext $openContext = null,
89
    ): self {
90
        return new self($exception, $exceptionId, $openContext);
91
    }
92
93
    private function createExceptionId(): string
94
    {
95
        $crc = crc32($this->exceptionAsString);
96
        $crcHex = dechex($crc & 0xFFFFFFFF); // Ensure positive hex value
97
98
        return 'e-bear-resource-' . $crcHex;
99
    }
100
101
    /** @return array<string, mixed> */
102
    #[Override]
103
    public function jsonSerialize(): array
104
    {
105
        $data = [
106
            'exceptionId' => $this->exceptionId,
107
            'exceptionAsString' => $this->exceptionAsString,
108
        ];
109
110
        // Only include profiling files if they exist
111
        if ($this->xhprofFile !== null) {
112
            $data['xhprofFile'] = $this->xhprofFile;
113
        }
114
115
        if ($this->xdebugTraceFile !== null) {
116
            $data['xdebugTraceFile'] = $this->xdebugTraceFile; // @codeCoverageIgnore
117
        }
118
119
        return $data;
120
    }
121
}
122