Context.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. do {
  91. $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
  92. } while (isset($array[$key]));
  93. $array[$key] = $key;
  94. do {
  95. $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
  96. } while (isset($array[$key]));
  97. $array[$key] = $this->objects;
  98. }
  99. return $key;
  100. }
  101. /**
  102. * @param object $object
  103. *
  104. * @return string
  105. */
  106. private function addObject($object)
  107. {
  108. if (!$this->objects->contains($object)) {
  109. $this->objects->attach($object);
  110. }
  111. return spl_object_hash($object);
  112. }
  113. /**
  114. * @param array $array
  115. *
  116. * @return int|false
  117. */
  118. private function containsArray(array &$array)
  119. {
  120. $end = array_slice($array, -2);
  121. return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false;
  122. }
  123. /**
  124. * @param object $value
  125. *
  126. * @return string|false
  127. */
  128. private function containsObject($value)
  129. {
  130. if ($this->objects->contains($value)) {
  131. return spl_object_hash($value);
  132. }
  133. return false;
  134. }
  135. public function __destruct()
  136. {
  137. foreach ($this->arrays as &$array) {
  138. if (is_array($array)) {
  139. array_pop($array);
  140. array_pop($array);
  141. }
  142. }
  143. }
  144. }