blob: 612b21f88e8b71cc8fe1a3a3a93ba0e95ee9ce61 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\VarDumper\Caster;
13
14use Symfony\Component\VarDumper\Cloner\Stub;
15
16/**
17 * Helper for filtering out properties in casters.
18 *
19 * @author Nicolas Grekas <p@tchwork.com>
20 *
21 * @final
22 */
23class Caster
24{
25 public const EXCLUDE_VERBOSE = 1;
26 public const EXCLUDE_VIRTUAL = 2;
27 public const EXCLUDE_DYNAMIC = 4;
28 public const EXCLUDE_PUBLIC = 8;
29 public const EXCLUDE_PROTECTED = 16;
30 public const EXCLUDE_PRIVATE = 32;
31 public const EXCLUDE_NULL = 64;
32 public const EXCLUDE_EMPTY = 128;
33 public const EXCLUDE_NOT_IMPORTANT = 256;
34 public const EXCLUDE_STRICT = 512;
35
36 public const PREFIX_VIRTUAL = "\0~\0";
37 public const PREFIX_DYNAMIC = "\0+\0";
38 public const PREFIX_PROTECTED = "\0*\0";
39
40 /**
41 * Casts objects to arrays and adds the dynamic property prefix.
42 *
43 * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not
44 *
45 * @return array The array-cast of the object, with prefixed dynamic properties
46 */
47 public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array
48 {
49 if ($hasDebugInfo) {
50 try {
51 $debugInfo = $obj->__debugInfo();
52 } catch (\Exception $e) {
53 // ignore failing __debugInfo()
54 $hasDebugInfo = false;
55 }
56 }
57
58 $a = $obj instanceof \Closure ? [] : (array) $obj;
59
60 if ($obj instanceof \__PHP_Incomplete_Class) {
61 return $a;
62 }
63
64 if ($a) {
65 static $publicProperties = [];
66 $debugClass = $debugClass ?? get_debug_type($obj);
67
68 $i = 0;
69 $prefixedKeys = [];
70 foreach ($a as $k => $v) {
71 if ("\0" !== ($k[0] ?? '')) {
72 if (!isset($publicProperties[$class])) {
73 foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
74 $publicProperties[$class][$prop->name] = true;
75 }
76 }
77 if (!isset($publicProperties[$class][$k])) {
78 $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k;
79 }
80 } elseif ($debugClass !== $class && 1 === strpos($k, $class)) {
81 $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0");
82 }
83 ++$i;
84 }
85 if ($prefixedKeys) {
86 $keys = array_keys($a);
87 foreach ($prefixedKeys as $i => $k) {
88 $keys[$i] = $k;
89 }
90 $a = array_combine($keys, $a);
91 }
92 }
93
94 if ($hasDebugInfo && \is_array($debugInfo)) {
95 foreach ($debugInfo as $k => $v) {
96 if (!isset($k[0]) || "\0" !== $k[0]) {
97 if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) {
98 continue;
99 }
100 $k = self::PREFIX_VIRTUAL.$k;
101 }
102
103 unset($a[$k]);
104 $a[$k] = $v;
105 }
106 }
107
108 return $a;
109 }
110
111 /**
112 * Filters out the specified properties.
113 *
114 * By default, a single match in the $filter bit field filters properties out, following an "or" logic.
115 * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed.
116 *
117 * @param array $a The array containing the properties to filter
118 * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out
119 * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set
120 * @param int &$count Set to the number of removed properties
121 *
122 * @return array The filtered array
123 */
124 public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array
125 {
126 $count = 0;
127
128 foreach ($a as $k => $v) {
129 $type = self::EXCLUDE_STRICT & $filter;
130
131 if (null === $v) {
132 $type |= self::EXCLUDE_NULL & $filter;
133 $type |= self::EXCLUDE_EMPTY & $filter;
134 } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) {
135 $type |= self::EXCLUDE_EMPTY & $filter;
136 }
137 if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) {
138 $type |= self::EXCLUDE_NOT_IMPORTANT;
139 }
140 if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) {
141 $type |= self::EXCLUDE_VERBOSE;
142 }
143
144 if (!isset($k[1]) || "\0" !== $k[0]) {
145 $type |= self::EXCLUDE_PUBLIC & $filter;
146 } elseif ('~' === $k[1]) {
147 $type |= self::EXCLUDE_VIRTUAL & $filter;
148 } elseif ('+' === $k[1]) {
149 $type |= self::EXCLUDE_DYNAMIC & $filter;
150 } elseif ('*' === $k[1]) {
151 $type |= self::EXCLUDE_PROTECTED & $filter;
152 } else {
153 $type |= self::EXCLUDE_PRIVATE & $filter;
154 }
155
156 if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) {
157 unset($a[$k]);
158 ++$count;
159 }
160 }
161
162 return $a;
163 }
164
165 public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array
166 {
167 if (isset($a['__PHP_Incomplete_Class_Name'])) {
168 $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')';
169 unset($a['__PHP_Incomplete_Class_Name']);
170 }
171
172 return $a;
173 }
174}