summaryrefslogtreecommitdiff
path: root/vendor/sebastian/recursion-context/src/Context.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sebastian/recursion-context/src/Context.php')
-rw-r--r--vendor/sebastian/recursion-context/src/Context.php186
1 files changed, 186 insertions, 0 deletions
diff --git a/vendor/sebastian/recursion-context/src/Context.php b/vendor/sebastian/recursion-context/src/Context.php
new file mode 100644
index 000000000..87fe7b04f
--- /dev/null
+++ b/vendor/sebastian/recursion-context/src/Context.php
@@ -0,0 +1,186 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of the Recursion Context package.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\RecursionContext;
+
+use const PHP_INT_MAX;
+use const PHP_INT_MIN;
+use function array_pop;
+use function array_slice;
+use function count;
+use function is_array;
+use function is_object;
+use function random_int;
+use function spl_object_hash;
+use SplObjectStorage;
+
+/**
+ * A context containing previously processed arrays and objects
+ * when recursively processing a value.
+ */
+final class Context
+{
+ /**
+ * @var array[]
+ */
+ private $arrays;
+
+ /**
+ * @var SplObjectStorage
+ */
+ private $objects;
+
+ /**
+ * Initialises the context.
+ */
+ public function __construct()
+ {
+ $this->arrays = [];
+ $this->objects = new SplObjectStorage;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ */
+ public function __destruct()
+ {
+ foreach ($this->arrays as &$array) {
+ if (is_array($array)) {
+ array_pop($array);
+ array_pop($array);
+ }
+ }
+ }
+
+ /**
+ * Adds a value to the context.
+ *
+ * @param array|object $value the value to add
+ *
+ * @throws InvalidArgumentException Thrown if $value is not an array or object
+ *
+ * @return bool|int|string the ID of the stored value, either as a string or integer
+ *
+ * @psalm-template T
+ * @psalm-param T $value
+ * @param-out T $value
+ */
+ public function add(&$value)
+ {
+ if (is_array($value)) {
+ return $this->addArray($value);
+ }
+
+ if (is_object($value)) {
+ return $this->addObject($value);
+ }
+
+ throw new InvalidArgumentException(
+ 'Only arrays and objects are supported'
+ );
+ }
+
+ /**
+ * Checks if the given value exists within the context.
+ *
+ * @param array|object $value the value to check
+ *
+ * @throws InvalidArgumentException Thrown if $value is not an array or object
+ *
+ * @return false|int|string the string or integer ID of the stored value if it has already been seen, or false if the value is not stored
+ *
+ * @psalm-template T
+ * @psalm-param T $value
+ * @param-out T $value
+ */
+ public function contains(&$value)
+ {
+ if (is_array($value)) {
+ return $this->containsArray($value);
+ }
+
+ if (is_object($value)) {
+ return $this->containsObject($value);
+ }
+
+ throw new InvalidArgumentException(
+ 'Only arrays and objects are supported'
+ );
+ }
+
+ /**
+ * @return bool|int
+ */
+ private function addArray(array &$array)
+ {
+ $key = $this->containsArray($array);
+
+ if ($key !== false) {
+ return $key;
+ }
+
+ $key = count($this->arrays);
+ $this->arrays[] = &$array;
+
+ if (!isset($array[PHP_INT_MAX]) && !isset($array[PHP_INT_MAX - 1])) {
+ $array[] = $key;
+ $array[] = $this->objects;
+ } else { /* cover the improbable case too */
+ do {
+ $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
+ } while (isset($array[$key]));
+
+ $array[$key] = $key;
+
+ do {
+ $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
+ } while (isset($array[$key]));
+
+ $array[$key] = $this->objects;
+ }
+
+ return $key;
+ }
+
+ /**
+ * @param object $object
+ */
+ private function addObject($object): string
+ {
+ if (!$this->objects->contains($object)) {
+ $this->objects->attach($object);
+ }
+
+ return spl_object_hash($object);
+ }
+
+ /**
+ * @return false|int
+ */
+ private function containsArray(array &$array)
+ {
+ $end = array_slice($array, -2);
+
+ return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false;
+ }
+
+ /**
+ * @param object $value
+ *
+ * @return false|string
+ */
+ private function containsObject($value)
+ {
+ if ($this->objects->contains($value)) {
+ return spl_object_hash($value);
+ }
+
+ return false;
+ }
+}