VarCloner.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\VarDumper\Cloner;
  11. /**
  12. * @author Nicolas Grekas <p@tchwork.com>
  13. */
  14. class VarCloner extends AbstractCloner
  15. {
  16. private static array $arrayCache = [];
  17. protected function doClone(mixed $var): array
  18. {
  19. $len = 1; // Length of $queue
  20. $pos = 0; // Number of cloned items past the minimum depth
  21. $refsCounter = 0; // Hard references counter
  22. $queue = [[$var]]; // This breadth-first queue is the return value
  23. $hardRefs = []; // Map of original zval ids to stub objects
  24. $objRefs = []; // Map of original object handles to their stub object counterpart
  25. $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning
  26. $resRefs = []; // Map of original resource handles to their stub object counterpart
  27. $values = []; // Map of stub objects' ids to original values
  28. $maxItems = $this->maxItems;
  29. $maxString = $this->maxString;
  30. $minDepth = $this->minDepth;
  31. $currentDepth = 0; // Current tree depth
  32. $currentDepthFinalIndex = 0; // Final $queue index for current tree depth
  33. $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached
  34. $cookie = (object) []; // Unique object used to detect hard references
  35. $a = null; // Array cast for nested structures
  36. $stub = null; // Stub capturing the main properties of an original item value
  37. // or null if the original value is used directly
  38. $arrayStub = new Stub();
  39. $arrayStub->type = Stub::TYPE_ARRAY;
  40. for ($i = 0; $i < $len; ++$i) {
  41. // Detect when we move on to the next tree depth
  42. if ($i > $currentDepthFinalIndex) {
  43. ++$currentDepth;
  44. $currentDepthFinalIndex = $len - 1;
  45. if ($currentDepth >= $minDepth) {
  46. $minimumDepthReached = true;
  47. }
  48. }
  49. $refs = $vals = $queue[$i];
  50. foreach ($vals as $k => $v) {
  51. // $v is the original value or a stub object in case of hard references
  52. $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null;
  53. if ($zvalRef) {
  54. $vals[$k] = &$stub; // Break hard references to make $queue completely
  55. unset($stub); // independent from the original structure
  56. if (null !== $vals[$k] = $hardRefs[$zvalRef] ?? null) {
  57. $v = $vals[$k];
  58. if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) {
  59. ++$v->value->refCount;
  60. }
  61. ++$v->refCount;
  62. continue;
  63. }
  64. $vals[$k] = new Stub();
  65. $vals[$k]->value = $v;
  66. $vals[$k]->handle = ++$refsCounter;
  67. $hardRefs[$zvalRef] = $vals[$k];
  68. }
  69. // Create $stub when the original value $v cannot be used directly
  70. // If $v is a nested structure, put that structure in array $a
  71. switch (true) {
  72. case null === $v:
  73. case \is_bool($v):
  74. case \is_int($v):
  75. case \is_float($v):
  76. continue 2;
  77. case \is_string($v):
  78. if ('' === $v) {
  79. continue 2;
  80. }
  81. if (!preg_match('//u', $v)) {
  82. $stub = new Stub();
  83. $stub->type = Stub::TYPE_STRING;
  84. $stub->class = Stub::STRING_BINARY;
  85. if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) {
  86. $stub->cut = $cut;
  87. $stub->value = substr($v, 0, -$cut);
  88. } else {
  89. $stub->value = $v;
  90. }
  91. } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) {
  92. $stub = new Stub();
  93. $stub->type = Stub::TYPE_STRING;
  94. $stub->class = Stub::STRING_UTF8;
  95. $stub->cut = $cut;
  96. $stub->value = mb_substr($v, 0, $maxString, 'UTF-8');
  97. } else {
  98. continue 2;
  99. }
  100. $a = null;
  101. break;
  102. case \is_array($v):
  103. if (!$v) {
  104. continue 2;
  105. }
  106. $stub = $arrayStub;
  107. $stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC;
  108. $a = $v;
  109. break;
  110. case \is_object($v):
  111. if (empty($objRefs[$h = spl_object_id($v)])) {
  112. $stub = new Stub();
  113. $stub->type = Stub::TYPE_OBJECT;
  114. $stub->class = $v::class;
  115. $stub->value = $v;
  116. $stub->handle = $h;
  117. $a = $this->castObject($stub, 0 < $i);
  118. if ($v !== $stub->value) {
  119. if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) {
  120. break;
  121. }
  122. $stub->handle = $h = spl_object_id($stub->value);
  123. }
  124. $stub->value = null;
  125. if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
  126. $stub->cut = \count($a);
  127. $a = null;
  128. }
  129. }
  130. if (empty($objRefs[$h])) {
  131. $objRefs[$h] = $stub;
  132. $objects[] = $v;
  133. } else {
  134. $stub = $objRefs[$h];
  135. ++$stub->refCount;
  136. $a = null;
  137. }
  138. break;
  139. default: // resource
  140. if (empty($resRefs[$h = (int) $v])) {
  141. $stub = new Stub();
  142. $stub->type = Stub::TYPE_RESOURCE;
  143. if ('Unknown' === $stub->class = @get_resource_type($v)) {
  144. $stub->class = 'Closed';
  145. }
  146. $stub->value = $v;
  147. $stub->handle = $h;
  148. $a = $this->castResource($stub, 0 < $i);
  149. $stub->value = null;
  150. if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
  151. $stub->cut = \count($a);
  152. $a = null;
  153. }
  154. }
  155. if (empty($resRefs[$h])) {
  156. $resRefs[$h] = $stub;
  157. } else {
  158. $stub = $resRefs[$h];
  159. ++$stub->refCount;
  160. $a = null;
  161. }
  162. break;
  163. }
  164. if ($a) {
  165. if (!$minimumDepthReached || 0 > $maxItems) {
  166. $queue[$len] = $a;
  167. $stub->position = $len++;
  168. } elseif ($pos < $maxItems) {
  169. if ($maxItems < $pos += \count($a)) {
  170. $a = \array_slice($a, 0, $maxItems - $pos, true);
  171. if ($stub->cut >= 0) {
  172. $stub->cut += $pos - $maxItems;
  173. }
  174. }
  175. $queue[$len] = $a;
  176. $stub->position = $len++;
  177. } elseif ($stub->cut >= 0) {
  178. $stub->cut += \count($a);
  179. $stub->position = 0;
  180. }
  181. }
  182. if ($arrayStub === $stub) {
  183. if ($arrayStub->cut) {
  184. $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position];
  185. $arrayStub->cut = 0;
  186. } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) {
  187. $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position];
  188. } else {
  189. self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position];
  190. }
  191. }
  192. if (!$zvalRef) {
  193. $vals[$k] = $stub;
  194. } else {
  195. $hardRefs[$zvalRef]->value = $stub;
  196. }
  197. }
  198. $queue[$i] = $vals;
  199. }
  200. foreach ($values as $h => $v) {
  201. $hardRefs[$h] = $v;
  202. }
  203. return $queue;
  204. }
  205. }