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@0: $this->arrays[] = &$array; Chris@0: Chris@0: return count($this->arrays) - 1; 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@0: $keys = array_keys($this->arrays, $array, true); Chris@0: $hash = '_Key_' . microtime(true); Chris@0: Chris@0: foreach ($keys as $key) { Chris@0: $this->arrays[$key][$hash] = $hash; Chris@0: Chris@0: if (isset($array[$hash]) && $array[$hash] === $hash) { Chris@0: unset($this->arrays[$key][$hash]); Chris@0: Chris@0: return $key; Chris@0: } Chris@0: Chris@0: unset($this->arrays[$key][$hash]); Chris@0: } Chris@0: Chris@0: return 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@0: }