Zur aktuellen Revision | Blame | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed
<?php declare(strict_types=1);/** This file is part of the Recursion Context package.** (c) Sebastian Bergmann <sebastian@phpunit.de>** 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;}}