4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\VarDumper\Cloner;
14 use Symfony\Component\VarDumper\Caster\Caster;
17 * @author Nicolas Grekas <p@tchwork.com>
19 class Data implements \ArrayAccess, \Countable, \IteratorAggregate
22 private $position = 0;
24 private $maxDepth = 20;
25 private $maxItemsPerDepth = -1;
26 private $useRefHandles = -1;
29 * @param array $data An array as returned by ClonerInterface::cloneVar()
31 public function __construct(array $data)
37 * @return string The type of the value
39 public function getType()
41 $item = $this->data[$this->position][$this->key];
43 if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
46 if (!$item instanceof Stub) {
47 return \gettype($item);
49 if (Stub::TYPE_STRING === $item->type) {
52 if (Stub::TYPE_ARRAY === $item->type) {
55 if (Stub::TYPE_OBJECT === $item->type) {
58 if (Stub::TYPE_RESOURCE === $item->type) {
59 return $item->class.' resource';
64 * @param bool $recursive Whether values should be resolved recursively or not
66 * @return string|int|float|bool|array|Data[]|null A native representation of the original value
68 public function getValue($recursive = false)
70 $item = $this->data[$this->position][$this->key];
72 if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
75 if (!($item = $this->getStub($item)) instanceof Stub) {
78 if (Stub::TYPE_STRING === $item->type) {
82 $children = $item->position ? $this->data[$item->position] : array();
84 foreach ($children as $k => $v) {
85 if ($recursive && !($v = $this->getStub($v)) instanceof Stub) {
88 $children[$k] = clone $this;
89 $children[$k]->key = $k;
90 $children[$k]->position = $item->position;
93 if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) {
94 $recursive = (array) $recursive;
95 if (isset($recursive[$v->position])) {
98 $recursive[$v->position] = true;
100 $children[$k] = $children[$k]->getValue($recursive);
107 public function count()
109 return \count($this->getValue());
112 public function getIterator()
114 if (!\is_array($value = $this->getValue())) {
115 throw new \LogicException(sprintf('%s object holds non-iterable type "%s".', self::class, \gettype($value)));
118 foreach ($value as $k => $v) {
123 public function __get($key)
125 if (null !== $data = $this->seek($key)) {
126 $item = $this->getStub($data->data[$data->position][$data->key]);
128 return $item instanceof Stub || array() === $item ? $data : $item;
132 public function __isset($key)
134 return null !== $this->seek($key);
137 public function offsetExists($key)
139 return $this->__isset($key);
142 public function offsetGet($key)
144 return $this->__get($key);
147 public function offsetSet($key, $value)
149 throw new \BadMethodCallException(self::class.' objects are immutable.');
152 public function offsetUnset($key)
154 throw new \BadMethodCallException(self::class.' objects are immutable.');
157 public function __toString()
159 $value = $this->getValue();
161 if (!\is_array($value)) {
162 return (string) $value;
165 return sprintf('%s (count=%d)', $this->getType(), \count($value));
169 * @return array The raw data structure
171 * @deprecated since version 3.3. Use array or object access instead.
173 public function getRawData()
175 @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the array or object access instead.', __METHOD__));
181 * Returns a depth limited clone of $this.
183 * @param int $maxDepth The max dumped depth level
185 * @return self A clone of $this
187 public function withMaxDepth($maxDepth)
190 $data->maxDepth = (int) $maxDepth;
196 * Limits the number of elements per depth level.
198 * @param int $maxItemsPerDepth The max number of items dumped per depth level
200 * @return self A clone of $this
202 public function withMaxItemsPerDepth($maxItemsPerDepth)
205 $data->maxItemsPerDepth = (int) $maxItemsPerDepth;
211 * Enables/disables objects' identifiers tracking.
213 * @param bool $useRefHandles False to hide global ref. handles
215 * @return self A clone of $this
217 public function withRefHandles($useRefHandles)
220 $data->useRefHandles = $useRefHandles ? -1 : 0;
226 * Seeks to a specific key in nested data structures.
228 * @param string|int $key The key to seek to
230 * @return self|null A clone of $this or null if the key is not set
232 public function seek($key)
234 $item = $this->data[$this->position][$this->key];
236 if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
237 $item = $item->value;
239 if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) {
244 switch ($item->type) {
245 case Stub::TYPE_OBJECT:
246 $keys[] = Caster::PREFIX_DYNAMIC.$key;
247 $keys[] = Caster::PREFIX_PROTECTED.$key;
248 $keys[] = Caster::PREFIX_VIRTUAL.$key;
249 $keys[] = "\0$item->class\0$key";
251 case Stub::TYPE_ARRAY:
252 case Stub::TYPE_RESOURCE:
259 $children = $this->data[$item->position];
261 foreach ($keys as $key) {
262 if (isset($children[$key]) || array_key_exists($key, $children)) {
265 $data->position = $item->position;
274 * Dumps data with a DumperInterface dumper.
276 public function dump(DumperInterface $dumper)
279 $this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]);
283 * Depth-first dumping of items.
285 * @param DumperInterface $dumper The dumper being used for dumping
286 * @param Cursor $cursor A cursor used for tracking dumper state position
287 * @param array &$refs A map of all references discovered while dumping
288 * @param mixed $item A Stub object or the original value being dumped
290 private function dumpItem($dumper, $cursor, &$refs, $item)
292 $cursor->refIndex = 0;
293 $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0;
294 $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0;
297 if (!$item instanceof Stub) {
298 $cursor->attr = array();
299 $type = \gettype($item);
300 if ($item && 'array' === $type) {
301 $item = $this->getStub($item);
303 } elseif (Stub::TYPE_REF === $item->type) {
305 if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) {
306 $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
310 $cursor->hardRefTo = $refs[$r];
311 $cursor->hardRefHandle = $this->useRefHandles & $item->handle;
312 $cursor->hardRefCount = $item->refCount;
314 $cursor->attr = $item->attr;
315 $type = $item->class ?: \gettype($item->value);
316 $item = $this->getStub($item->value);
318 if ($item instanceof Stub) {
319 if ($item->refCount) {
320 if (!isset($refs[$r = $item->handle])) {
321 $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
325 $cursor->softRefTo = $refs[$r];
327 $cursor->softRefHandle = $this->useRefHandles & $item->handle;
328 $cursor->softRefCount = $item->refCount;
329 $cursor->attr = $item->attr;
332 if ($item->position && $firstSeen) {
333 $children = $this->data[$item->position];
337 $cut += \count($children);
344 switch ($item->type) {
345 case Stub::TYPE_STRING:
346 $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut);
349 case Stub::TYPE_ARRAY:
351 $item->type = $item->class;
352 $item->class = $item->value;
354 case Stub::TYPE_OBJECT:
355 case Stub::TYPE_RESOURCE:
356 $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth;
357 $dumper->enterHash($cursor, $item->type, $item->class, $withChildren);
359 if ($cursor->skipChildren) {
360 $withChildren = false;
363 $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class);
365 } elseif ($children && 0 <= $cut) {
366 $cut += \count($children);
368 $cursor->skipChildren = false;
369 $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut);
373 throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type));
375 } elseif ('array' === $type) {
376 $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false);
377 $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0);
378 } elseif ('string' === $type) {
379 $dumper->dumpString($cursor, $item, false, 0);
381 $dumper->dumpScalar($cursor, $type, $item);
386 * Dumps children of hash structures.
388 * @param DumperInterface $dumper
389 * @param Cursor $parentCursor The cursor of the parent hash
390 * @param array &$refs A map of all references discovered while dumping
391 * @param array $children The children to dump
392 * @param int $hashCut The number of items removed from the original hash
393 * @param string $hashType A Cursor::HASH_* const
394 * @param bool $dumpKeys Whether keys should be dumped or not
396 * @return int The final number of removed items
398 private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType, $dumpKeys)
400 $cursor = clone $parentCursor;
402 $cursor->hashType = $hashType;
403 $cursor->hashIndex = 0;
404 $cursor->hashLength = \count($children);
405 $cursor->hashCut = $hashCut;
406 foreach ($children as $key => $child) {
407 $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key);
408 $cursor->hashKey = $dumpKeys ? $key : null;
409 $this->dumpItem($dumper, $cursor, $refs, $child);
410 if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) {
411 $parentCursor->stop = true;
413 return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut;
420 private function getStub($item)
422 if (!$item || !\is_array($item)) {
427 $stub->type = Stub::TYPE_ARRAY;
428 foreach ($item as $stub->class => $stub->position) {
430 if (isset($item[0])) {
431 $stub->cut = $item[0];
433 $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0);