Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace SebastianBergmann\RecursionContext; Chris@0: Chris@0: /** Chris@0: * A context containing previously processed arrays and objects Chris@0: * when recursively processing a value. Chris@0: */ Chris@0: final class Context Chris@0: { Chris@0: /** Chris@0: * @var array[] Chris@0: */ Chris@0: private $arrays; Chris@0: Chris@0: /** Chris@0: * @var \SplObjectStorage Chris@0: */ Chris@0: private $objects; Chris@0: Chris@0: /** Chris@0: * Initialises the context Chris@0: */ Chris@0: public function __construct() Chris@0: { Chris@0: $this->arrays = array(); Chris@0: $this->objects = new \SplObjectStorage; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a value to the context. Chris@0: * Chris@0: * @param array|object $value The value to add. Chris@0: * Chris@0: * @return int|string The ID of the stored value, either as a string or integer. Chris@0: * Chris@0: * @throws InvalidArgumentException Thrown if $value is not an array or object Chris@0: */ Chris@0: public function add(&$value) Chris@0: { Chris@0: if (is_array($value)) { Chris@0: return $this->addArray($value); Chris@0: } elseif (is_object($value)) { Chris@0: return $this->addObject($value); Chris@0: } Chris@0: Chris@0: throw new InvalidArgumentException( Chris@0: 'Only arrays and objects are supported' Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if the given value exists within the context. Chris@0: * Chris@0: * @param array|object $value The value to check. Chris@0: * Chris@0: * @return int|string|false The string or integer ID of the stored value if it has already been seen, or false if the value is not stored. Chris@0: * Chris@0: * @throws InvalidArgumentException Thrown if $value is not an array or object Chris@0: */ Chris@0: public function contains(&$value) Chris@0: { Chris@0: if (is_array($value)) { Chris@0: return $this->containsArray($value); Chris@0: } elseif (is_object($value)) { Chris@0: return $this->containsObject($value); Chris@0: } Chris@0: Chris@0: throw new InvalidArgumentException( Chris@0: 'Only arrays and objects are supported' Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param array $array Chris@0: * Chris@0: * @return bool|int Chris@0: */ Chris@0: private function addArray(array &$array) Chris@0: { Chris@0: $key = $this->containsArray($array); Chris@0: Chris@0: if ($key !== false) { Chris@0: return $key; Chris@0: } Chris@0: Chris@14: $key = count($this->arrays); Chris@0: $this->arrays[] = &$array; Chris@0: Chris@14: if (!isset($array[PHP_INT_MAX]) && !isset($array[PHP_INT_MAX - 1])) { Chris@14: $array[] = $key; Chris@14: $array[] = $this->objects; Chris@14: } else { /* cover the improbable case too */ Chris@14: do { Chris@14: $key = random_int(PHP_INT_MIN, PHP_INT_MAX); Chris@14: } while (isset($array[$key])); Chris@14: Chris@14: $array[$key] = $key; Chris@14: Chris@14: do { Chris@14: $key = random_int(PHP_INT_MIN, PHP_INT_MAX); Chris@14: } while (isset($array[$key])); Chris@14: Chris@14: $array[$key] = $this->objects; Chris@14: } Chris@14: Chris@14: return $key; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param object $object Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: private function addObject($object) Chris@0: { Chris@0: if (!$this->objects->contains($object)) { Chris@0: $this->objects->attach($object); Chris@0: } Chris@0: Chris@0: return spl_object_hash($object); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param array $array Chris@0: * Chris@0: * @return int|false Chris@0: */ Chris@0: private function containsArray(array &$array) Chris@0: { Chris@14: $end = array_slice($array, -2); Chris@0: Chris@14: return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param object $value Chris@0: * Chris@0: * @return string|false Chris@0: */ Chris@0: private function containsObject($value) Chris@0: { Chris@0: if ($this->objects->contains($value)) { Chris@0: return spl_object_hash($value); Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@14: Chris@14: public function __destruct() Chris@14: { Chris@14: foreach ($this->arrays as &$array) { Chris@14: if (is_array($array)) { Chris@14: array_pop($array); Chris@14: array_pop($array); Chris@14: } Chris@14: } Chris@14: } Chris@0: }