1: <?php declare(strict_types=1);
2:
3: namespace Salient\Utility;
4:
5: /**
6: * Get data from the call stack
7: *
8: * @api
9: */
10: final class Debug extends AbstractUtility
11: {
12: /**
13: * Get a description of the (caller's) caller from debug_backtrace()
14: *
15: * Returns an associative array with zero or more of the following values.
16: * Separators are added as needed to allow concatenation to a caller string.
17: *
18: * - `namespace`
19: * - `class`
20: * - `file`
21: * - `function`
22: * - `line`
23: *
24: * For an earlier frame in the call stack, set `$depth` to `1` or higher.
25: *
26: * @return array{namespace?:string,class?:string,file?:string,0?:string,function?:string,1?:string,line?:int}
27: */
28: public static function getCaller(int $depth = 0): array
29: {
30: // 0. called us (function = getCaller)
31: // 1. called them (function = ourCaller)
32: // 2. used the name of their caller (function = callsOurCaller)
33: //
34: // Use namespace, class and function from 2 if possible, otherwise file
35: // and line from 1
36: $frames = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, $depth + 3);
37: $file = $frames[$depth + 1]['file'] ?? null;
38: $line = $frames[$depth + 1]['line'] ?? null;
39: $beforeLine = $line !== null ? ':' : null;
40:
41: if (isset($frames[$depth + 2]['function'])) {
42: $frame = $frames[$depth + 2];
43: // - PHP 8.4: `{closure:Salient\Tests\Utility\Debug\GetCallerClass::getCallback():31}`
44: // - Earlier: `Salient\Tests\Utility\Debug\{closure}`
45: if (Str::startsWith($frame['function'], '{closure:')) {
46: $closure = implode(':', array_slice(explode(':', $frame['function']), 1, -1));
47: }
48: if (isset($frame['class'])) {
49: $namespace = Get::namespace($frame['class']);
50: $class = Get::basename($frame['class']);
51: } else {
52: $namespace = Get::namespace($closure ?? $frame['function']);
53: $class = '';
54: }
55: // `function` and `class` are both namespaced for closures in
56: // namespaced classes
57: $function = isset($closure)
58: ? '{closure}'
59: : Get::basename($frame['function']);
60: if ($namespace !== '') {
61: $namespace .= '\\';
62: }
63: if ($class !== '' || $namespace !== '') {
64: $file = null;
65: }
66: return Arr::whereNotEmpty([
67: 'namespace' => $namespace,
68: 'class' => $class,
69: 'file' => $file,
70: $frame['type'] ?? ($file !== null ? '::' : null),
71: 'function' => $function,
72: $beforeLine,
73: 'line' => $line,
74: ]);
75: }
76:
77: if (isset($frames[$depth + 1])) {
78: return Arr::whereNotEmpty([
79: 'file' => $file,
80: $beforeLine,
81: 'line' => $line,
82: ]);
83: }
84:
85: return [];
86: }
87: }
88: