namespace Symfony\Component\VarDumper\Cloner;
+use Symfony\Component\VarDumper\Caster\Caster;
+
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
-class Data
+class Data implements \ArrayAccess, \Countable, \IteratorAggregate
{
private $data;
+ private $position = 0;
+ private $key = 0;
private $maxDepth = 20;
private $maxItemsPerDepth = -1;
private $useRefHandles = -1;
/**
- * @param array $data A array as returned by ClonerInterface::cloneVar()
+ * @param array $data An array as returned by ClonerInterface::cloneVar()
*/
public function __construct(array $data)
{
$this->data = $data;
}
+ /**
+ * @return string The type of the value
+ */
+ public function getType()
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!$item instanceof Stub) {
+ return \gettype($item);
+ }
+ if (Stub::TYPE_STRING === $item->type) {
+ return 'string';
+ }
+ if (Stub::TYPE_ARRAY === $item->type) {
+ return 'array';
+ }
+ if (Stub::TYPE_OBJECT === $item->type) {
+ return $item->class;
+ }
+ if (Stub::TYPE_RESOURCE === $item->type) {
+ return $item->class.' resource';
+ }
+ }
+
+ /**
+ * @param bool $recursive Whether values should be resolved recursively or not
+ *
+ * @return string|int|float|bool|array|Data[]|null A native representation of the original value
+ */
+ public function getValue($recursive = false)
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!($item = $this->getStub($item)) instanceof Stub) {
+ return $item;
+ }
+ if (Stub::TYPE_STRING === $item->type) {
+ return $item->value;
+ }
+
+ $children = $item->position ? $this->data[$item->position] : array();
+
+ foreach ($children as $k => $v) {
+ if ($recursive && !($v = $this->getStub($v)) instanceof Stub) {
+ continue;
+ }
+ $children[$k] = clone $this;
+ $children[$k]->key = $k;
+ $children[$k]->position = $item->position;
+
+ if ($recursive) {
+ if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) {
+ $recursive = (array) $recursive;
+ if (isset($recursive[$v->position])) {
+ continue;
+ }
+ $recursive[$v->position] = true;
+ }
+ $children[$k] = $children[$k]->getValue($recursive);
+ }
+ }
+
+ return $children;
+ }
+
+ public function count()
+ {
+ return \count($this->getValue());
+ }
+
+ public function getIterator()
+ {
+ if (!\is_array($value = $this->getValue())) {
+ throw new \LogicException(sprintf('%s object holds non-iterable type "%s".', self::class, \gettype($value)));
+ }
+
+ foreach ($value as $k => $v) {
+ yield $k => $v;
+ }
+ }
+
+ public function __get($key)
+ {
+ if (null !== $data = $this->seek($key)) {
+ $item = $this->getStub($data->data[$data->position][$data->key]);
+
+ return $item instanceof Stub || array() === $item ? $data : $item;
+ }
+ }
+
+ public function __isset($key)
+ {
+ return null !== $this->seek($key);
+ }
+
+ public function offsetExists($key)
+ {
+ return $this->__isset($key);
+ }
+
+ public function offsetGet($key)
+ {
+ return $this->__get($key);
+ }
+
+ public function offsetSet($key, $value)
+ {
+ throw new \BadMethodCallException(self::class.' objects are immutable.');
+ }
+
+ public function offsetUnset($key)
+ {
+ throw new \BadMethodCallException(self::class.' objects are immutable.');
+ }
+
+ public function __toString()
+ {
+ $value = $this->getValue();
+
+ if (!\is_array($value)) {
+ return (string) $value;
+ }
+
+ return sprintf('%s (count=%d)', $this->getType(), \count($value));
+ }
+
/**
* @return array The raw data structure
+ *
+ * @deprecated since version 3.3. Use array or object access instead.
*/
public function getRawData()
{
+ @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__));
+
return $this->data;
}
}
/**
- * Returns a depth limited clone of $this.
+ * Seeks to a specific key in nested data structures.
*
- * @param int $maxDepth The max dumped depth level
- * @param int $maxItemsPerDepth The max number of items dumped per depth level
- * @param bool $useRefHandles False to hide ref. handles
+ * @param string|int $key The key to seek to
*
- * @return self A depth limited clone of $this
- *
- * @deprecated since Symfony 2.7, to be removed in 3.0. Use withMaxDepth, withMaxItemsPerDepth or withRefHandles instead.
+ * @return self|null A clone of $this or null if the key is not set
*/
- public function getLimitedClone($maxDepth, $maxItemsPerDepth, $useRefHandles = true)
+ public function seek($key)
{
- @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0. Use withMaxDepth, withMaxItemsPerDepth or withRefHandles methods instead.', E_USER_DEPRECATED);
+ $item = $this->data[$this->position][$this->key];
- $data = clone $this;
- $data->maxDepth = (int) $maxDepth;
- $data->maxItemsPerDepth = (int) $maxItemsPerDepth;
- $data->useRefHandles = $useRefHandles ? -1 : 0;
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) {
+ return;
+ }
+ $keys = array($key);
+
+ switch ($item->type) {
+ case Stub::TYPE_OBJECT:
+ $keys[] = Caster::PREFIX_DYNAMIC.$key;
+ $keys[] = Caster::PREFIX_PROTECTED.$key;
+ $keys[] = Caster::PREFIX_VIRTUAL.$key;
+ $keys[] = "\0$item->class\0$key";
+ // no break
+ case Stub::TYPE_ARRAY:
+ case Stub::TYPE_RESOURCE:
+ break;
+ default:
+ return;
+ }
+
+ $data = null;
+ $children = $this->data[$item->position];
+
+ foreach ($keys as $key) {
+ if (isset($children[$key]) || array_key_exists($key, $children)) {
+ $data = clone $this;
+ $data->key = $key;
+ $data->position = $item->position;
+ break;
+ }
+ }
return $data;
}
public function dump(DumperInterface $dumper)
{
$refs = array(0);
- $this->dumpItem($dumper, new Cursor(), $refs, $this->data[0][0]);
+ $this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]);
}
/**
$firstSeen = true;
if (!$item instanceof Stub) {
- $type = gettype($item);
+ $cursor->attr = array();
+ $type = \gettype($item);
+ if ($item && 'array' === $type) {
+ $item = $this->getStub($item);
+ }
} elseif (Stub::TYPE_REF === $item->type) {
if ($item->handle) {
if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) {
$cursor->hardRefHandle = $this->useRefHandles & $item->handle;
$cursor->hardRefCount = $item->refCount;
}
- $type = $item->class ?: gettype($item->value);
- $item = $item->value;
+ $cursor->attr = $item->attr;
+ $type = $item->class ?: \gettype($item->value);
+ $item = $this->getStub($item->value);
}
if ($item instanceof Stub) {
if ($item->refCount) {
}
$cursor->softRefHandle = $this->useRefHandles & $item->handle;
$cursor->softRefCount = $item->refCount;
+ $cursor->attr = $item->attr;
$cut = $item->cut;
if ($item->position && $firstSeen) {
if ($cursor->stop) {
if ($cut >= 0) {
- $cut += count($children);
+ $cut += \count($children);
}
$children = array();
}
$item = clone $item;
$item->type = $item->class;
$item->class = $item->value;
- // No break;
+ // no break
case Stub::TYPE_OBJECT:
case Stub::TYPE_RESOURCE:
$withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth;
$dumper->enterHash($cursor, $item->type, $item->class, $withChildren);
if ($withChildren) {
- $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type);
+ if ($cursor->skipChildren) {
+ $withChildren = false;
+ $cut = -1;
+ } else {
+ $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class);
+ }
} elseif ($children && 0 <= $cut) {
- $cut += count($children);
+ $cut += \count($children);
}
+ $cursor->skipChildren = false;
$dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut);
break;
* @param array $children The children to dump
* @param int $hashCut The number of items removed from the original hash
* @param string $hashType A Cursor::HASH_* const
+ * @param bool $dumpKeys Whether keys should be dumped or not
*
* @return int The final number of removed items
*/
- private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType)
+ private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType, $dumpKeys)
{
$cursor = clone $parentCursor;
++$cursor->depth;
$cursor->hashType = $hashType;
$cursor->hashIndex = 0;
- $cursor->hashLength = count($children);
+ $cursor->hashLength = \count($children);
$cursor->hashCut = $hashCut;
foreach ($children as $key => $child) {
$cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key);
- $cursor->hashKey = $key;
+ $cursor->hashKey = $dumpKeys ? $key : null;
$this->dumpItem($dumper, $cursor, $refs, $child);
if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) {
$parentCursor->stop = true;
return $hashCut;
}
+
+ private function getStub($item)
+ {
+ if (!$item || !\is_array($item)) {
+ return $item;
+ }
+
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_ARRAY;
+ foreach ($item as $stub->class => $stub->position) {
+ }
+ if (isset($item[0])) {
+ $stub->cut = $item[0];
+ }
+ $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0);
+
+ return $stub;
+ }
}