1<?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\Cloner; 13 14/** 15 * @author Nicolas Grekas <p@tchwork.com> 16 */ 17class VarCloner extends AbstractCloner 18{ 19 private static $gid; 20 private static $arrayCache = []; 21 22 /** 23 * {@inheritdoc} 24 */ 25 protected function doClone($var) 26 { 27 $len = 1; // Length of $queue 28 $pos = 0; // Number of cloned items past the minimum depth 29 $refsCounter = 0; // Hard references counter 30 $queue = [[$var]]; // This breadth-first queue is the return value 31 $indexedArrays = []; // Map of queue indexes that hold numerically indexed arrays 32 $hardRefs = []; // Map of original zval ids to stub objects 33 $objRefs = []; // Map of original object handles to their stub object counterpart 34 $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning 35 $resRefs = []; // Map of original resource handles to their stub object counterpart 36 $values = []; // Map of stub objects' ids to original values 37 $maxItems = $this->maxItems; 38 $maxString = $this->maxString; 39 $minDepth = $this->minDepth; 40 $currentDepth = 0; // Current tree depth 41 $currentDepthFinalIndex = 0; // Final $queue index for current tree depth 42 $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached 43 $cookie = (object) []; // Unique object used to detect hard references 44 $a = null; // Array cast for nested structures 45 $stub = null; // Stub capturing the main properties of an original item value 46 // or null if the original value is used directly 47 48 if (!$gid = self::$gid) { 49 $gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable 50 } 51 $arrayStub = new Stub(); 52 $arrayStub->type = Stub::TYPE_ARRAY; 53 $fromObjCast = false; 54 55 for ($i = 0; $i < $len; ++$i) { 56 // Detect when we move on to the next tree depth 57 if ($i > $currentDepthFinalIndex) { 58 ++$currentDepth; 59 $currentDepthFinalIndex = $len - 1; 60 if ($currentDepth >= $minDepth) { 61 $minimumDepthReached = true; 62 } 63 } 64 65 $refs = $vals = $queue[$i]; 66 if (\PHP_VERSION_ID < 70200 && empty($indexedArrays[$i])) { 67 // see https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts 68 foreach ($vals as $k => $v) { 69 if (\is_int($k)) { 70 continue; 71 } 72 foreach ([$k => true] as $gk => $gv) { 73 } 74 if ($gk !== $k) { 75 $fromObjCast = true; 76 $refs = $vals = array_values($queue[$i]); 77 break; 78 } 79 } 80 } 81 foreach ($vals as $k => $v) { 82 // $v is the original value or a stub object in case of hard references 83 84 if (\PHP_VERSION_ID >= 70400) { 85 $zvalIsRef = null !== \ReflectionReference::fromArrayElement($vals, $k); 86 } else { 87 $refs[$k] = $cookie; 88 $zvalIsRef = $vals[$k] === $cookie; 89 } 90 91 if ($zvalIsRef) { 92 $vals[$k] = &$stub; // Break hard references to make $queue completely 93 unset($stub); // independent from the original structure 94 if ($v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { 95 $vals[$k] = $refs[$k] = $v; 96 if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { 97 ++$v->value->refCount; 98 } 99 ++$v->refCount; 100 continue; 101 } 102 $refs[$k] = $vals[$k] = new Stub(); 103 $refs[$k]->value = $v; 104 $h = spl_object_id($refs[$k]); 105 $hardRefs[$h] = &$refs[$k]; 106 $values[$h] = $v; 107 $vals[$k]->handle = ++$refsCounter; 108 } 109 // Create $stub when the original value $v can not be used directly 110 // If $v is a nested structure, put that structure in array $a 111 switch (true) { 112 case null === $v: 113 case \is_bool($v): 114 case \is_int($v): 115 case \is_float($v): 116 continue 2; 117 118 case \is_string($v): 119 if ('' === $v) { 120 continue 2; 121 } 122 if (!preg_match('//u', $v)) { 123 $stub = new Stub(); 124 $stub->type = Stub::TYPE_STRING; 125 $stub->class = Stub::STRING_BINARY; 126 if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { 127 $stub->cut = $cut; 128 $stub->value = substr($v, 0, -$cut); 129 } else { 130 $stub->value = $v; 131 } 132 } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { 133 $stub = new Stub(); 134 $stub->type = Stub::TYPE_STRING; 135 $stub->class = Stub::STRING_UTF8; 136 $stub->cut = $cut; 137 $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); 138 } else { 139 continue 2; 140 } 141 $a = null; 142 break; 143 144 case \is_array($v): 145 if (!$v) { 146 continue 2; 147 } 148 $stub = $arrayStub; 149 $stub->class = Stub::ARRAY_INDEXED; 150 151 $j = -1; 152 foreach ($v as $gk => $gv) { 153 if ($gk !== ++$j) { 154 $stub->class = Stub::ARRAY_ASSOC; 155 break; 156 } 157 } 158 $a = $v; 159 160 if (Stub::ARRAY_ASSOC === $stub->class) { 161 // Copies of $GLOBALS have very strange behavior, 162 // let's detect them with some black magic 163 $a[$gid] = true; 164 165 // Happens with copies of $GLOBALS 166 if (isset($v[$gid])) { 167 unset($v[$gid]); 168 $a = []; 169 foreach ($v as $gk => &$gv) { 170 $a[$gk] = &$gv; 171 } 172 unset($gv); 173 } else { 174 $a = $v; 175 } 176 } elseif (\PHP_VERSION_ID < 70200) { 177 $indexedArrays[$len] = true; 178 } 179 break; 180 181 case \is_object($v): 182 case $v instanceof \__PHP_Incomplete_Class: 183 if (empty($objRefs[$h = spl_object_id($v)])) { 184 $stub = new Stub(); 185 $stub->type = Stub::TYPE_OBJECT; 186 $stub->class = \get_class($v); 187 $stub->value = $v; 188 $stub->handle = $h; 189 $a = $this->castObject($stub, 0 < $i); 190 if ($v !== $stub->value) { 191 if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { 192 break; 193 } 194 $stub->handle = $h = spl_object_id($stub->value); 195 } 196 $stub->value = null; 197 if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { 198 $stub->cut = \count($a); 199 $a = null; 200 } 201 } 202 if (empty($objRefs[$h])) { 203 $objRefs[$h] = $stub; 204 $objects[] = $v; 205 } else { 206 $stub = $objRefs[$h]; 207 ++$stub->refCount; 208 $a = null; 209 } 210 break; 211 212 default: // resource 213 if (empty($resRefs[$h = (int) $v])) { 214 $stub = new Stub(); 215 $stub->type = Stub::TYPE_RESOURCE; 216 if ('Unknown' === $stub->class = @get_resource_type($v)) { 217 $stub->class = 'Closed'; 218 } 219 $stub->value = $v; 220 $stub->handle = $h; 221 $a = $this->castResource($stub, 0 < $i); 222 $stub->value = null; 223 if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { 224 $stub->cut = \count($a); 225 $a = null; 226 } 227 } 228 if (empty($resRefs[$h])) { 229 $resRefs[$h] = $stub; 230 } else { 231 $stub = $resRefs[$h]; 232 ++$stub->refCount; 233 $a = null; 234 } 235 break; 236 } 237 238 if ($a) { 239 if (!$minimumDepthReached || 0 > $maxItems) { 240 $queue[$len] = $a; 241 $stub->position = $len++; 242 } elseif ($pos < $maxItems) { 243 if ($maxItems < $pos += \count($a)) { 244 $a = \array_slice($a, 0, $maxItems - $pos); 245 if ($stub->cut >= 0) { 246 $stub->cut += $pos - $maxItems; 247 } 248 } 249 $queue[$len] = $a; 250 $stub->position = $len++; 251 } elseif ($stub->cut >= 0) { 252 $stub->cut += \count($a); 253 $stub->position = 0; 254 } 255 } 256 257 if ($arrayStub === $stub) { 258 if ($arrayStub->cut) { 259 $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; 260 $arrayStub->cut = 0; 261 } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { 262 $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; 263 } else { 264 self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; 265 } 266 } 267 268 if ($zvalIsRef) { 269 $refs[$k]->value = $stub; 270 } else { 271 $vals[$k] = $stub; 272 } 273 } 274 275 if ($fromObjCast) { 276 $fromObjCast = false; 277 $refs = $vals; 278 $vals = []; 279 $j = -1; 280 foreach ($queue[$i] as $k => $v) { 281 foreach ([$k => true] as $gk => $gv) { 282 } 283 if ($gk !== $k) { 284 $vals = (object) $vals; 285 $vals->{$k} = $refs[++$j]; 286 $vals = (array) $vals; 287 } else { 288 $vals[$k] = $refs[++$j]; 289 } 290 } 291 } 292 293 $queue[$i] = $vals; 294 } 295 296 foreach ($values as $h => $v) { 297 $hardRefs[$h] = $v; 298 } 299 300 return $queue; 301 } 302} 303