blob: 53f4461d0df80daf77ffc3e381203b9e158a0774 [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
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020044 */
45 public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array
46 {
47 if ($hasDebugInfo) {
48 try {
49 $debugInfo = $obj->__debugInfo();
50 } catch (\Exception $e) {
51 // ignore failing __debugInfo()
52 $hasDebugInfo = false;
53 }
54 }
55
56 $a = $obj instanceof \Closure ? [] : (array) $obj;
57
58 if ($obj instanceof \__PHP_Incomplete_Class) {
59 return $a;
60 }
61
62 if ($a) {
63 static $publicProperties = [];
64 $debugClass = $debugClass ?? get_debug_type($obj);
65
66 $i = 0;
67 $prefixedKeys = [];
68 foreach ($a as $k => $v) {
69 if ("\0" !== ($k[0] ?? '')) {
70 if (!isset($publicProperties[$class])) {
71 foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
72 $publicProperties[$class][$prop->name] = true;
73 }
74 }
75 if (!isset($publicProperties[$class][$k])) {
76 $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k;
77 }
78 } elseif ($debugClass !== $class && 1 === strpos($k, $class)) {
79 $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0");
80 }
81 ++$i;
82 }
83 if ($prefixedKeys) {
84 $keys = array_keys($a);
85 foreach ($prefixedKeys as $i => $k) {
86 $keys[$i] = $k;
87 }
88 $a = array_combine($keys, $a);
89 }
90 }
91
92 if ($hasDebugInfo && \is_array($debugInfo)) {
93 foreach ($debugInfo as $k => $v) {
94 if (!isset($k[0]) || "\0" !== $k[0]) {
95 if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) {
96 continue;
97 }
98 $k = self::PREFIX_VIRTUAL.$k;
99 }
100
101 unset($a[$k]);
102 $a[$k] = $v;
103 }
104 }
105
106 return $a;
107 }
108
109 /**
110 * Filters out the specified properties.
111 *
112 * By default, a single match in the $filter bit field filters properties out, following an "or" logic.
113 * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed.
114 *
115 * @param array $a The array containing the properties to filter
116 * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out
117 * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set
118 * @param int &$count Set to the number of removed properties
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200119 */
120 public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array
121 {
122 $count = 0;
123
124 foreach ($a as $k => $v) {
125 $type = self::EXCLUDE_STRICT & $filter;
126
127 if (null === $v) {
128 $type |= self::EXCLUDE_NULL & $filter;
129 $type |= self::EXCLUDE_EMPTY & $filter;
130 } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) {
131 $type |= self::EXCLUDE_EMPTY & $filter;
132 }
133 if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) {
134 $type |= self::EXCLUDE_NOT_IMPORTANT;
135 }
136 if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) {
137 $type |= self::EXCLUDE_VERBOSE;
138 }
139
140 if (!isset($k[1]) || "\0" !== $k[0]) {
141 $type |= self::EXCLUDE_PUBLIC & $filter;
142 } elseif ('~' === $k[1]) {
143 $type |= self::EXCLUDE_VIRTUAL & $filter;
144 } elseif ('+' === $k[1]) {
145 $type |= self::EXCLUDE_DYNAMIC & $filter;
146 } elseif ('*' === $k[1]) {
147 $type |= self::EXCLUDE_PROTECTED & $filter;
148 } else {
149 $type |= self::EXCLUDE_PRIVATE & $filter;
150 }
151
152 if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) {
153 unset($a[$k]);
154 ++$count;
155 }
156 }
157
158 return $a;
159 }
160
161 public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array
162 {
163 if (isset($a['__PHP_Incomplete_Class_Name'])) {
164 $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')';
165 unset($a['__PHP_Incomplete_Class_Name']);
166 }
167
168 return $a;
169 }
170}