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