Context.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <?php
  2. /*
  3. * This file is part of the Recursion Context package.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\RecursionContext;
  11. /**
  12. * A context containing previously processed arrays and objects
  13. * when recursively processing a value.
  14. */
  15. final class Context
  16. {
  17. /**
  18. * @var array[]
  19. */
  20. private $arrays;
  21. /**
  22. * @var \SplObjectStorage
  23. */
  24. private $objects;
  25. /**
  26. * Initialises the context
  27. */
  28. public function __construct()
  29. {
  30. $this->arrays = array();
  31. $this->objects = new \SplObjectStorage;
  32. }
  33. /**
  34. * Adds a value to the context.
  35. *
  36. * @param array|object $value The value to add.
  37. *
  38. * @return int|string The ID of the stored value, either as a string or integer.
  39. *
  40. * @throws InvalidArgumentException Thrown if $value is not an array or object
  41. */
  42. public function add(&$value)
  43. {
  44. if (is_array($value)) {
  45. return $this->addArray($value);
  46. } elseif (is_object($value)) {
  47. return $this->addObject($value);
  48. }
  49. throw new InvalidArgumentException(
  50. 'Only arrays and objects are supported'
  51. );
  52. }
  53. /**
  54. * Checks if the given value exists within the context.
  55. *
  56. * @param array|object $value The value to check.
  57. *
  58. * @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.
  59. *
  60. * @throws InvalidArgumentException Thrown if $value is not an array or object
  61. */
  62. public function contains(&$value)
  63. {
  64. if (is_array($value)) {
  65. return $this->containsArray($value);
  66. } elseif (is_object($value)) {
  67. return $this->containsObject($value);
  68. }
  69. throw new InvalidArgumentException(
  70. 'Only arrays and objects are supported'
  71. );
  72. }
  73. /**
  74. * @param array $array
  75. *
  76. * @return bool|int
  77. */
  78. private function addArray(array &$array)
  79. {
  80. $key = $this->containsArray($array);
  81. if ($key !== false) {
  82. return $key;
  83. }
  84. $key = count($this->arrays);
  85. $this->arrays[] = &$array;
  86. if (!isset($array[PHP_INT_MAX]) && !isset($array[PHP_INT_MAX - 1])) {
  87. $array[] = $key;
  88. $array[] = $this->objects;
  89. } else { /* cover the improbable case too */
  90. // @codeCoverageIgnoreStart
  91. do {
  92. $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
  93. } while (isset($array[$key]));
  94. $array[$key] = $key;
  95. do {
  96. $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
  97. } while (isset($array[$key]));
  98. $array[$key] = $this->objects;
  99. // @codeCoverageIgnoreEnd
  100. }
  101. return $key;
  102. }
  103. /**
  104. * @param object $object
  105. *
  106. * @return string
  107. */
  108. private function addObject($object)
  109. {
  110. if (!$this->objects->contains($object)) {
  111. $this->objects->attach($object);
  112. }
  113. return spl_object_hash($object);
  114. }
  115. /**
  116. * @param array $array
  117. *
  118. * @return int|false
  119. */
  120. private function containsArray(array &$array)
  121. {
  122. $end = array_slice($array, -2);
  123. return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false;
  124. }
  125. /**
  126. * @param object $value
  127. *
  128. * @return string|false
  129. */
  130. private function containsObject($value)
  131. {
  132. if ($this->objects->contains($value)) {
  133. return spl_object_hash($value);
  134. }
  135. return false;
  136. }
  137. /**
  138. * @codeCoverageIgnore
  139. */
  140. public function __destruct()
  141. {
  142. foreach ($this->arrays as &$array) {
  143. if (is_array($array)) {
  144. array_pop($array);
  145. array_pop($array);
  146. }
  147. }
  148. }
  149. }