You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
14 KiB
311 lines
14 KiB
<?php |
|
|
|
/* |
|
* This file is part of the Symfony package. |
|
* |
|
* (c) Fabien Potencier <fabien@symfony.com> |
|
* |
|
* For the full copyright and license information, please view the LICENSE |
|
* file that was distributed with this source code. |
|
*/ |
|
|
|
namespace Symfony\Component\VarDumper\Cloner; |
|
|
|
/** |
|
* @author Nicolas Grekas <p@tchwork.com> |
|
*/ |
|
class VarCloner extends AbstractCloner |
|
{ |
|
private static $gid; |
|
private static $arrayCache = []; |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
protected function doClone($var) |
|
{ |
|
$len = 1; // Length of $queue |
|
$pos = 0; // Number of cloned items past the minimum depth |
|
$refsCounter = 0; // Hard references counter |
|
$queue = [[$var]]; // This breadth-first queue is the return value |
|
$hardRefs = []; // Map of original zval ids to stub objects |
|
$objRefs = []; // Map of original object handles to their stub object counterpart |
|
$objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning |
|
$resRefs = []; // Map of original resource handles to their stub object counterpart |
|
$values = []; // Map of stub objects' ids to original values |
|
$maxItems = $this->maxItems; |
|
$maxString = $this->maxString; |
|
$minDepth = $this->minDepth; |
|
$currentDepth = 0; // Current tree depth |
|
$currentDepthFinalIndex = 0; // Final $queue index for current tree depth |
|
$minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached |
|
$cookie = (object) []; // Unique object used to detect hard references |
|
$a = null; // Array cast for nested structures |
|
$stub = null; // Stub capturing the main properties of an original item value |
|
// or null if the original value is used directly |
|
|
|
if (!$gid = self::$gid) { |
|
$gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable |
|
} |
|
$arrayStub = new Stub(); |
|
$arrayStub->type = Stub::TYPE_ARRAY; |
|
$fromObjCast = false; |
|
|
|
for ($i = 0; $i < $len; ++$i) { |
|
// Detect when we move on to the next tree depth |
|
if ($i > $currentDepthFinalIndex) { |
|
++$currentDepth; |
|
$currentDepthFinalIndex = $len - 1; |
|
if ($currentDepth >= $minDepth) { |
|
$minimumDepthReached = true; |
|
} |
|
} |
|
|
|
$refs = $vals = $queue[$i]; |
|
foreach ($vals as $k => $v) { |
|
// $v is the original value or a stub object in case of hard references |
|
|
|
if (\PHP_VERSION_ID >= 70400) { |
|
$zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; |
|
} else { |
|
$refs[$k] = $cookie; |
|
$zvalRef = $vals[$k] === $cookie; |
|
} |
|
|
|
if ($zvalRef) { |
|
$vals[$k] = &$stub; // Break hard references to make $queue completely |
|
unset($stub); // independent from the original structure |
|
if (\PHP_VERSION_ID >= 70400 ? null !== $vals[$k] = $hardRefs[$zvalRef] ?? null : $v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { |
|
if (\PHP_VERSION_ID >= 70400) { |
|
$v = $vals[$k]; |
|
} else { |
|
$refs[$k] = $vals[$k] = $v; |
|
} |
|
if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { |
|
++$v->value->refCount; |
|
} |
|
++$v->refCount; |
|
continue; |
|
} |
|
$vals[$k] = new Stub(); |
|
$vals[$k]->value = $v; |
|
$vals[$k]->handle = ++$refsCounter; |
|
|
|
if (\PHP_VERSION_ID >= 70400) { |
|
$hardRefs[$zvalRef] = $vals[$k]; |
|
} else { |
|
$refs[$k] = $vals[$k]; |
|
$h = spl_object_id($refs[$k]); |
|
$hardRefs[$h] = &$refs[$k]; |
|
$values[$h] = $v; |
|
} |
|
} |
|
// Create $stub when the original value $v cannot be used directly |
|
// If $v is a nested structure, put that structure in array $a |
|
switch (true) { |
|
case null === $v: |
|
case \is_bool($v): |
|
case \is_int($v): |
|
case \is_float($v): |
|
continue 2; |
|
case \is_string($v): |
|
if ('' === $v) { |
|
continue 2; |
|
} |
|
if (!preg_match('//u', $v)) { |
|
$stub = new Stub(); |
|
$stub->type = Stub::TYPE_STRING; |
|
$stub->class = Stub::STRING_BINARY; |
|
if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { |
|
$stub->cut = $cut; |
|
$stub->value = substr($v, 0, -$cut); |
|
} else { |
|
$stub->value = $v; |
|
} |
|
} elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { |
|
$stub = new Stub(); |
|
$stub->type = Stub::TYPE_STRING; |
|
$stub->class = Stub::STRING_UTF8; |
|
$stub->cut = $cut; |
|
$stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); |
|
} else { |
|
continue 2; |
|
} |
|
$a = null; |
|
break; |
|
|
|
case \is_array($v): |
|
if (!$v) { |
|
continue 2; |
|
} |
|
$stub = $arrayStub; |
|
|
|
if (\PHP_VERSION_ID >= 80100) { |
|
$stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; |
|
$a = $v; |
|
break; |
|
} |
|
|
|
$stub->class = Stub::ARRAY_INDEXED; |
|
|
|
$j = -1; |
|
foreach ($v as $gk => $gv) { |
|
if ($gk !== ++$j) { |
|
$stub->class = Stub::ARRAY_ASSOC; |
|
$a = $v; |
|
$a[$gid] = true; |
|
break; |
|
} |
|
} |
|
|
|
// Copies of $GLOBALS have very strange behavior, |
|
// let's detect them with some black magic |
|
if (isset($v[$gid])) { |
|
unset($v[$gid]); |
|
$a = []; |
|
foreach ($v as $gk => &$gv) { |
|
if ($v === $gv && (\PHP_VERSION_ID < 70400 || !isset($hardRefs[\ReflectionReference::fromArrayElement($v, $gk)->getId()]))) { |
|
unset($v); |
|
$v = new Stub(); |
|
$v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0]; |
|
$v->handle = -1; |
|
if (\PHP_VERSION_ID >= 70400) { |
|
$gv = &$a[$gk]; |
|
$hardRefs[\ReflectionReference::fromArrayElement($a, $gk)->getId()] = &$gv; |
|
} else { |
|
$gv = &$hardRefs[spl_object_id($v)]; |
|
} |
|
$gv = $v; |
|
} |
|
|
|
$a[$gk] = &$gv; |
|
} |
|
unset($gv); |
|
} else { |
|
$a = $v; |
|
} |
|
break; |
|
|
|
case \is_object($v): |
|
if (empty($objRefs[$h = spl_object_id($v)])) { |
|
$stub = new Stub(); |
|
$stub->type = Stub::TYPE_OBJECT; |
|
$stub->class = \get_class($v); |
|
$stub->value = $v; |
|
$stub->handle = $h; |
|
$a = $this->castObject($stub, 0 < $i); |
|
if ($v !== $stub->value) { |
|
if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { |
|
break; |
|
} |
|
$stub->handle = $h = spl_object_id($stub->value); |
|
} |
|
$stub->value = null; |
|
if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { |
|
$stub->cut = \count($a); |
|
$a = null; |
|
} |
|
} |
|
if (empty($objRefs[$h])) { |
|
$objRefs[$h] = $stub; |
|
$objects[] = $v; |
|
} else { |
|
$stub = $objRefs[$h]; |
|
++$stub->refCount; |
|
$a = null; |
|
} |
|
break; |
|
|
|
default: // resource |
|
if (empty($resRefs[$h = (int) $v])) { |
|
$stub = new Stub(); |
|
$stub->type = Stub::TYPE_RESOURCE; |
|
if ('Unknown' === $stub->class = @get_resource_type($v)) { |
|
$stub->class = 'Closed'; |
|
} |
|
$stub->value = $v; |
|
$stub->handle = $h; |
|
$a = $this->castResource($stub, 0 < $i); |
|
$stub->value = null; |
|
if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { |
|
$stub->cut = \count($a); |
|
$a = null; |
|
} |
|
} |
|
if (empty($resRefs[$h])) { |
|
$resRefs[$h] = $stub; |
|
} else { |
|
$stub = $resRefs[$h]; |
|
++$stub->refCount; |
|
$a = null; |
|
} |
|
break; |
|
} |
|
|
|
if ($a) { |
|
if (!$minimumDepthReached || 0 > $maxItems) { |
|
$queue[$len] = $a; |
|
$stub->position = $len++; |
|
} elseif ($pos < $maxItems) { |
|
if ($maxItems < $pos += \count($a)) { |
|
$a = \array_slice($a, 0, $maxItems - $pos, true); |
|
if ($stub->cut >= 0) { |
|
$stub->cut += $pos - $maxItems; |
|
} |
|
} |
|
$queue[$len] = $a; |
|
$stub->position = $len++; |
|
} elseif ($stub->cut >= 0) { |
|
$stub->cut += \count($a); |
|
$stub->position = 0; |
|
} |
|
} |
|
|
|
if ($arrayStub === $stub) { |
|
if ($arrayStub->cut) { |
|
$stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; |
|
$arrayStub->cut = 0; |
|
} elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { |
|
$stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; |
|
} else { |
|
self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; |
|
} |
|
} |
|
|
|
if (!$zvalRef) { |
|
$vals[$k] = $stub; |
|
} elseif (\PHP_VERSION_ID >= 70400) { |
|
$hardRefs[$zvalRef]->value = $stub; |
|
} else { |
|
$refs[$k]->value = $stub; |
|
} |
|
} |
|
|
|
if ($fromObjCast) { |
|
$fromObjCast = false; |
|
$refs = $vals; |
|
$vals = []; |
|
$j = -1; |
|
foreach ($queue[$i] as $k => $v) { |
|
foreach ([$k => true] as $gk => $gv) { |
|
} |
|
if ($gk !== $k) { |
|
$vals = (object) $vals; |
|
$vals->{$k} = $refs[++$j]; |
|
$vals = (array) $vals; |
|
} else { |
|
$vals[$k] = $refs[++$j]; |
|
} |
|
} |
|
} |
|
|
|
$queue[$i] = $vals; |
|
} |
|
|
|
foreach ($values as $h => $v) { |
|
$hardRefs[$h] = $v; |
|
} |
|
|
|
return $queue; |
|
} |
|
}
|
|
|