src/Debug.php source

1 <?php
2
3 declare(strict_types=1);
4
5 namespace Serializor;
6
7 use Closure;
8 use ReflectionClass;
9 use ReflectionFunction;
10 use ReflectionReference;
11
12 class Debug
13 {
14 private static array $longMemory = [];
15 private static int $count = 0;
16
17 public static function clear(): void
18 {
19 self::$longMemory = [];
20 self::$count = 0;
21 }
22
23 public static function dump(mixed &$value, int $indent = 0, string|int|null $key = null, array $stackMemory = [], array &$shortMemory = []): void
24 {
25 try {
26 $ind = \str_repeat(' ', $indent);
27 if (\is_object($value)) {
28 $refId = 'object' . \spl_object_id($value);
29 } else {
30 $refId = ReflectionReference::fromArrayElement([&$value], 0)->getId() . (\is_array($value) || \is_object($value) ? \spl_object_hash((object) $value) : '');
31 }
32 if (isset(self::$longMemory['@' . $refId])) {
33 $varId = self::$longMemory['@' . $refId];
34 } else {
35 $varId = '{' . self::$count++ . '}';
36 self::$longMemory['@' . $refId] = $varId;
37 }
38 $output = isset($shortMemory['output'][$varId]);
39 $shortMemory['output'][$varId] = true;
40 $seen = isset($stackMemory['seen'][$varId]);
41 $stackMemory['seen'][$varId] = true;
42 $varIdStr = ($seen ? '@' . $varId : "$varId") . ' ';
43 if ($seen && (\is_array($value) || \is_object($value))) {
44 echo $ind . ($key !== null ? (trim(var_export($key, true)) . " = ") : '') . "$varIdStr*RECURSION* (" . get_debug_type($value) . ")\n";
45 return;
46 } elseif ($output) {
47 if (\is_scalar($value) || $value === null) {
48 $valueStr = trim(var_export($value, true)) . ' ';
49 } else {
50 $valueStr = '';
51 }
52 echo $ind . ($key !== null ? (trim(var_export($key, true)) . " = ") : '') . "$varIdStr*REF* $valueStr(" . get_debug_type($value) . ")\n";
53 return;
54 } else
55 if ($key !== null) {
56 echo $ind . trim(var_export($key, true)) . " = " . $varIdStr;
57 } else {
58 echo $ind . $varIdStr;
59 }
60 if (is_array($value)) {
61
62 if (empty($value)) {
63 echo "array[]\n";
64 return;
65 } else {
66 echo "array[\n";
67 }
68 foreach ($value as $k => &$v) {
69 self::dump($v, $indent + 1, $k, $stackMemory, $shortMemory);
70 }
71 echo $ind . "]\n";
72 } elseif ($value instanceof Closure) {
73 $rc = new ReflectionFunction($value);
74 if ($rc->getName() === '{closure}') {
75 echo "Closure";
76 } else {
77 echo $rc->getName();
78 }
79
80 $args = [];
81 foreach ($rc->getParameters() as $rp) {
82 $arg = '';
83 if ($rp->hasType()) {
84 $arg .= $rp->getType()->getName() . ' ';
85 }
86 if ($rp->isPassedByReference()) {
87 $arg .= '&';
88 }
89 if ($rp->isVariadic()) {
90 $arg .= '...';
91 }
92 $arg .= '$' . $rp->getName();
93 if ($rp->isDefaultValueAvailable()) {
94 '=' . trim(var_export($rp->getDefaultValue(), true));
95 }
96 $args[] = $arg;
97 }
98 echo "(" . implode(", ", $args) . ") {";
99 ob_start();
100 $self = $rc->getClosureThis();
101 if ($self !== null) {
102 self::dump($self, $indent + 1, '$this', $stackMemory, $shortMemory);
103 }
104 $scopeClass = $rc->getClosureScopeClass()?->getName();
105 if ($scopeClass !== null) {
106 self::dump($scopeClass, $indent + 1, 'self', $stackMemory, $shortMemory);
107 }
108 $uses = $rc->getClosureUsedVariables();
109 if (!empty($uses)) {
110 self::dump($uses, $indent + 1, 'use', $stackMemory, $shortMemory);
111 }
112 $c = ob_get_contents();
113 ob_end_clean();
114 if ($c === '') {
115 echo "}\n";
116 } else {
117 echo "\n$c$ind}\n";
118 }
119 } elseif (\is_object($value)) {
120 echo \get_class($value) . " {\n";
121 $rc = new ReflectionClass($value);
122 foreach ($rc->getProperties() as $rp) {
123 if ($rp->isStatic()) {
124 continue;
125 }
126 $propValue = Closure::bind(function () use ($rp) {
127 $name = $rp->getName();
128 return $this->$name;
129 }, $value, $rc->getName())();
130 self::dump($propValue, $indent + 1, $rp->getName(), $stackMemory, $shortMemory);
131 }
132 echo $ind . "}\n";
133 } elseif ($value === null) {
134 echo trim(var_export($value, true)) . "\n";
135 } else {
136 echo trim(var_export($value, true)) . " (" . \get_debug_type($value) . ")\n";
137 }
138 } finally {
139 }
140 }
141 }
142
143 class DebugBox
144 {
145 public function __construct(
146 public readonly mixed $value,
147 public readonly string $refId
148 ) {}
149 }
150