123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- <?php declare(strict_types=1);
- /*
- * This file is part of sebastian/recursion-context.
- *
- * (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_key_exists;
- use function array_pop;
- use function array_slice;
- use function count;
- use function is_array;
- use function random_int;
- use function spl_object_hash;
- use SplObjectStorage;
- final class Context
- {
- private array $arrays = [];
- private SplObjectStorage $objects;
- public function __construct()
- {
- $this->objects = new SplObjectStorage;
- }
- /**
- * @codeCoverageIgnore
- */
- public function __destruct()
- {
- foreach ($this->arrays as &$array) {
- if (is_array($array)) {
- array_pop($array);
- array_pop($array);
- }
- }
- }
- /**
- * @psalm-template T
- *
- * @psalm-param T $value
- *
- * @param-out T $value
- */
- public function add(object|array &$value): int|string|false
- {
- if (is_array($value)) {
- return $this->addArray($value);
- }
- return $this->addObject($value);
- }
- /**
- * @psalm-template T
- *
- * @psalm-param T $value
- *
- * @param-out T $value
- */
- public function contains(object|array &$value): int|string|false
- {
- if (is_array($value)) {
- return $this->containsArray($value);
- }
- return $this->containsObject($value);
- }
- private function addArray(array &$array): int
- {
- $key = $this->containsArray($array);
- if ($key !== false) {
- return $key;
- }
- $key = count($this->arrays);
- $this->arrays[] = &$array;
- if (!array_key_exists(PHP_INT_MAX, $array) && !array_key_exists(PHP_INT_MAX - 1, $array)) {
- $array[] = $key;
- $array[] = $this->objects;
- } else {
- /* Cover the improbable case, too.
- *
- * Note that array_slice() (used in containsArray()) will return the
- * last two values added, *not necessarily* the highest integer keys
- * in the array. Therefore, the order of these writes to $array is
- * important, but the actual keys used is not. */
- do {
- /** @noinspection PhpUnhandledExceptionInspection */
- $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
- } while (array_key_exists($key, $array));
- $array[$key] = $key;
- do {
- /** @noinspection PhpUnhandledExceptionInspection */
- $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
- } while (array_key_exists($key, $array));
- $array[$key] = $this->objects;
- }
- return $key;
- }
- private function addObject(object $object): string
- {
- if (!$this->objects->contains($object)) {
- $this->objects->attach($object);
- }
- return spl_object_hash($object);
- }
- private function containsArray(array $array): int|false
- {
- $end = array_slice($array, -2);
- return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false;
- }
- private function containsObject(object $value): string|false
- {
- if ($this->objects->contains($value)) {
- return spl_object_hash($value);
- }
- return false;
- }
- }
|