Passed
Pull Request — 1.x (#334)
by Akihito
02:26
created

PhpProfile   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 79
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 27
c 1
b 0
f 1
dl 0
loc 79
rs 10
wmc 12

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A jsonSerialize() 0 5 1
B collectBacktrace() 0 49 9
A capture() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource\SemanticLog\Profile;
6
7
use JsonSerializable;
8
use Override;
9
10
use function array_filter;
11
use function array_map;
12
use function array_slice;
13
use function array_values;
14
use function debug_backtrace;
15
use function str_contains;
16
17
use const DEBUG_BACKTRACE_IGNORE_ARGS;
18
19
final class PhpProfile implements JsonSerializable
20
{
21
    /** @param array<int, array{file?: string, line?: int, class?: string, function: string, type?: string}> $backtrace */
22
    public function __construct(
23
        public readonly array $backtrace = [],
24
    ) {
25
    }
26
27
    public static function capture(int $backtraceLimit = 10): self
28
    {
29
        return new self(
30
            backtrace: self::collectBacktrace($backtraceLimit),
31
        );
32
    }
33
34
    /**
35
     * Collect backtrace information excluding framework internals
36
     *
37
     * @param int $limit Maximum number of stack frames to collect
38
     *
39
     * @return array<int, array{file?: string, line?: int, class?: string, function: string, type?: string}>
40
     */
41
    private static function collectBacktrace(int $limit): array
42
    {
43
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
44
45
        // Filter out framework internal calls to focus on user code
46
        $filtered = array_filter($backtrace, static function (array $frame): bool {
47
            if (! isset($frame['file'])) {
48
                return false;
49
            }
50
51
            // Skip BEAR.Resource internal files
52
            if (str_contains($frame['file'], '/BEAR/Resource/')) {
53
                return false;
54
            }
55
56
            // Skip vendor files except user application
57
            if (str_contains($frame['file'], '/vendor/') && ! str_contains($frame['file'], '/tests/')) {
58
                return false;
59
            }
60
61
            // Skip PHPUnit test framework files
62
            return ! str_contains($frame['file'], '/phpunit/');
63
        });
64
65
        // Reset array keys and limit results
66
        $result = array_slice(array_values($filtered), 0, $limit);
67
68
        // Clean up the frames to only include relevant information
69
        return array_map(static function (array $frame): array {
70
            $clean = ['function' => $frame['function']];
71
72
            if (isset($frame['file'])) {
73
                $clean['file'] = $frame['file'];
74
            }
75
76
            if (isset($frame['line'])) {
77
                $clean['line'] = $frame['line'];
78
            }
79
80
            if (isset($frame['class'])) {
81
                $clean['class'] = $frame['class'];
82
            }
83
84
            if (isset($frame['type'])) {
85
                $clean['type'] = $frame['type'];
86
            }
87
88
            return $clean;
89
        }, $result);
90
    }
91
92
    /** @return array<string, mixed> */
93
    #[Override]
94
    public function jsonSerialize(): array
95
    {
96
        return [
97
            'backtrace' => $this->backtrace,
98
        ];
99
    }
100
}
101