Context.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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. * @return int|string The ID of the stored value, either as
  38. * a string or integer.
  39. * @throws InvalidArgumentException Thrown if $value is not an array or
  40. * object
  41. */
  42. public function add(&$value)
  43. {
  44. if (is_array($value)) {
  45. return $this->addArray($value);
  46. }
  47. else if (is_object($value)) {
  48. return $this->addObject($value);
  49. }
  50. throw new InvalidArgumentException(
  51. 'Only arrays and objects are supported'
  52. );
  53. }
  54. /**
  55. * Checks if the given value exists within the context.
  56. *
  57. * @param array|object $value The value to check.
  58. * @return int|string|false The string or integer ID of the stored
  59. * value if it has already been seen, or
  60. * false if the value is not stored.
  61. * @throws InvalidArgumentException Thrown if $value is not an array or
  62. * object
  63. */
  64. public function contains(&$value)
  65. {
  66. if (is_array($value)) {
  67. return $this->containsArray($value);
  68. }
  69. else if (is_object($value)) {
  70. return $this->containsObject($value);
  71. }
  72. throw new InvalidArgumentException(
  73. 'Only arrays and objects are supported'
  74. );
  75. }
  76. /**
  77. * @param array $array
  78. * @return bool|int
  79. */
  80. private function addArray(array &$array)
  81. {
  82. $key = $this->containsArray($array);
  83. if ($key !== false) {
  84. return $key;
  85. }
  86. $this->arrays[] = &$array;
  87. return count($this->arrays) - 1;
  88. }
  89. /**
  90. * @param object $object
  91. * @return string
  92. */
  93. private function addObject($object)
  94. {
  95. if (!$this->objects->contains($object)) {
  96. $this->objects->attach($object);
  97. }
  98. return spl_object_hash($object);
  99. }
  100. /**
  101. * @param array $array
  102. * @return int|false
  103. */
  104. private function containsArray(array &$array)
  105. {
  106. $keys = array_keys($this->arrays, $array, true);
  107. $hash = '_Key_' . microtime(true);
  108. foreach ($keys as $key) {
  109. $this->arrays[$key][$hash] = $hash;
  110. if (isset($array[$hash]) && $array[$hash] === $hash) {
  111. unset($this->arrays[$key][$hash]);
  112. return $key;
  113. }
  114. unset($this->arrays[$key][$hash]);
  115. }
  116. return false;
  117. }
  118. /**
  119. * @param object $value
  120. * @return string|false
  121. */
  122. private function containsObject($value)
  123. {
  124. if ($this->objects->contains($value)) {
  125. return spl_object_hash($value);
  126. }
  127. return false;
  128. }
  129. }