1: <?php declare(strict_types=1);
2:
3: namespace Salient\Utility;
4:
5: /**
6: * Get information about 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: * The return values below, for example, would implode to:
25: * - `Salient\Tests\Utility\Debug\GetCallerClass->getCallerViaMethod:23`
26: * - `/path/to/tests/fixtures/Toolkit/Utility/Debug/GetCallerFile1.php::{closure}:29`
27: *
28: * ```
29: * <?php
30: * [
31: * 'namespace' => 'Salient\\Tests\\Utility\\Debug\\',
32: * 'class' => 'GetCallerClass',
33: * '->',
34: * 'function' => 'getCallerViaMethod',
35: * ':',
36: * 'line' => 23,
37: * ];
38: *
39: * [
40: * 'file' => '/path/to/tests/fixtures/Toolkit/Utility/Debug/GetCallerFile1.php',
41: * '::',
42: * 'function' => '{closure}',
43: * ':',
44: * 'line' => 29,
45: * ];
46: * ```
47: *
48: * For an earlier frame in the call stack, set `$depth` to `1` or higher.
49: *
50: * @return array{namespace?:string,class?:string,file?:string,0?:string,function?:string,1?:string,line?:int}
51: */
52: public static function getCaller(int $depth = 0): array
53: {
54: // 0. called us (function = getCaller)
55: // 1. called them (function = ourCaller)
56: // 2. used the name of their caller (function = callsOurCaller)
57: //
58: // Use namespace, class and function from 2 if possible, otherwise file
59: // and line from 1
60: $frames = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, $depth + 3);
61: $file = $frames[$depth + 1]['file'] ?? null;
62: $line = $frames[$depth + 1]['line'] ?? null;
63: $beforeLine = $line !== null ? ':' : null;
64:
65: if (isset($frames[$depth + 2]['function'])) {
66: $frame = $frames[$depth + 2];
67: if (isset($frame['class'])) {
68: $namespace = Get::namespace($frame['class']);
69: $class = Get::basename($frame['class']);
70: } else {
71: $namespace = Get::namespace($frame['function']);
72: $class = '';
73: }
74: // NB: `function` and `class` are both namespaced for closures in
75: // namespaced classes
76: $function = Get::basename($frame['function']);
77: if ($namespace !== '') {
78: $namespace .= '\\';
79: }
80: if ($class !== '' || $namespace !== '') {
81: $file = null;
82: }
83: return Arr::whereNotEmpty([
84: 'namespace' => $namespace,
85: 'class' => $class,
86: 'file' => $file,
87: $frame['type'] ?? ($file !== null ? '::' : null),
88: 'function' => $function,
89: $beforeLine,
90: 'line' => $line,
91: ]);
92: }
93:
94: if (isset($frames[$depth + 1])) {
95: return Arr::whereNotEmpty([
96: 'file' => $file,
97: $beforeLine,
98: 'line' => $line,
99: ]);
100: }
101:
102: return [];
103: }
104: }
105: