+ /**
+ * @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));
+ }
+