8a9fba76ce8c105b19d14f336248482bf072f9f7
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / ObjectStorage.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * The storage for objects. It ensures the uniqueness of an object in the storage. It's a remake of the
19 * SplObjectStorage introduced in PHP 5.3.
20 *
21 * Opposed to the SplObjectStorage the ObjectStorage does not implement the Serializable interface.
22 */
23 class ObjectStorage implements \Countable, \Iterator, \ArrayAccess, ObjectMonitoringInterface
24 {
25 /**
26 * This field is only needed to make debugging easier:
27 * If you call current() on a class that implements Iterator, PHP will return the first field of the object
28 * instead of calling the current() method of the interface.
29 * We use this unusual behavior of PHP to return the warning below in this case.
30 *
31 * @var string
32 */
33 private $warning = 'You should never see this warning. If you do, you probably used PHP array functions like current() on the TYPO3\\CMS\\Extbase\\Persistence\\ObjectStorage. To retrieve the first result, you can use the rewind() and current() methods.';
34
35 /**
36 * An array holding the objects and the stored information. The key of the array items ist the
37 * spl_object_hash of the given object.
38 *
39 * array(
40 * spl_object_hash =>
41 * array(
42 * 'obj' => $object,
43 * 'inf' => $information
44 * )
45 * )
46 *
47 * @var array
48 */
49 protected $storage = [];
50
51 /**
52 * A flag indication if the object storage was modified after reconstitution (eg. by adding a new object)
53 *
54 * @var bool
55 */
56 protected $isModified = false;
57
58 /**
59 * An array holding the internal position the object was added.
60 * The object entry is unsetted when the object gets removed from the objectstorage
61 *
62 * @var array
63 */
64 protected $addedObjectsPositions = [];
65
66 /**
67 * An array holding the internal position the object was added before, when it would
68 * be removed from the objectstorage
69 *
70 * @var array
71 */
72 protected $removedObjectsPositions = [];
73
74 /**
75 * An internal var holding the count of added objects to be stored as position.
76 * It would be resetted, when all objects will be removed from the objectstorage
77 *
78 * @var int
79 */
80 protected $positionCounter = 0;
81
82 /**
83 * Rewinds the iterator to the first storage element.
84 *
85 * @return void
86 */
87 public function rewind()
88 {
89 reset($this->storage);
90 }
91
92 /**
93 * Checks if the array pointer of the storage points to a valid position.
94 *
95 * @return bool
96 */
97 public function valid()
98 {
99 return current($this->storage) !== false;
100 }
101
102 /**
103 * Returns the index at which the iterator currently is.
104 *
105 * This is different from the SplObjectStorage as the key in this implementation is the object hash (string).
106 *
107 * @return string The index corresponding to the position of the iterator.
108 */
109 public function key()
110 {
111 return key($this->storage);
112 }
113
114 /**
115 * Returns the current storage entry.
116 *
117 * @return object The object at the current iterator position.
118 */
119 public function current()
120 {
121 $item = current($this->storage);
122 return $item['obj'];
123 }
124
125 /**
126 * Moves to the next entry.
127 *
128 * @return void
129 */
130 public function next()
131 {
132 next($this->storage);
133 }
134
135 /**
136 * Returns the number of objects in the storage.
137 *
138 * @return int The number of objects in the storage.
139 */
140 public function count()
141 {
142 return count($this->storage);
143 }
144
145 /**
146 * Associates data to an object in the storage. offsetSet() is an alias of attach().
147 *
148 * @param object $object The object to add.
149 * @param mixed $information The data to associate with the object.
150 * @return void
151 */
152 public function offsetSet($object, $information)
153 {
154 $this->isModified = true;
155 $this->storage[spl_object_hash($object)] = ['obj' => $object, 'inf' => $information];
156
157 $this->positionCounter++;
158 $this->addedObjectsPositions[spl_object_hash($object)] = $this->positionCounter;
159 }
160
161 /**
162 * Checks whether an object exists in the storage.
163 *
164 * @param object $object The object to look for.
165 * @return bool
166 */
167 public function offsetExists($object)
168 {
169 return is_object($object) && isset($this->storage[spl_object_hash($object)]);
170 }
171
172 /**
173 * Removes an object from the storage. offsetUnset() is an alias of detach().
174 *
175 * @param object $object The object to remove.
176 * @return void
177 */
178 public function offsetUnset($object)
179 {
180 $this->isModified = true;
181 unset($this->storage[spl_object_hash($object)]);
182
183 if (empty($this->storage)) {
184 $this->positionCounter = 0;
185 }
186
187 $this->removedObjectsPositions[spl_object_hash($object)] = $this->addedObjectsPositions[spl_object_hash($object)];
188 unset($this->addedObjectsPositions[spl_object_hash($object)]);
189 }
190
191 /**
192 * Returns the data associated with an object.
193 *
194 * @param object $object The object to look for.
195 * @return mixed The data associated with an object in the storage.
196 */
197 public function offsetGet($object)
198 {
199 return $this->storage[spl_object_hash($object)]['inf'];
200 }
201
202 /**
203 * Checks if the storage contains a specific object.
204 *
205 * @param object $object The object to look for.
206 * @return bool
207 */
208 public function contains($object)
209 {
210 return $this->offsetExists($object);
211 }
212
213 /**
214 * Adds an object in the storage, and optionaly associate it to some data.
215 *
216 * @param object $object The object to add.
217 * @param mixed $information The data to associate with the object.
218 * @return void
219 */
220 public function attach($object, $information = null)
221 {
222 $this->offsetSet($object, $information);
223 }
224
225 /**
226 * Removes an object from the storage.
227 *
228 * @param object $object The object to remove.
229 * @return void
230 */
231 public function detach($object)
232 {
233 $this->offsetUnset($object);
234 }
235
236 /**
237 * Returns the data, or info, associated with the object pointed by the current iterator position.
238 *
239 * @return mixed The data associated with the current iterator position.
240 */
241 public function getInfo()
242 {
243 $item = current($this->storage);
244 return $item['inf'];
245 }
246
247 /**
248 * Associates data, or info, with the object currently pointed to by the iterator.
249 *
250 * @param mixed $data
251 * @return void
252 */
253 public function setInfo($data)
254 {
255 $this->isModified = true;
256 $key = key($this->storage);
257 $this->storage[$key]['inf'] = $data;
258 }
259
260 /**
261 * Adds all objects-data pairs from a different storage in the current storage.
262 *
263 * @param ObjectStorage $objectStorage
264 * @return void
265 */
266 public function addAll(ObjectStorage $objectStorage)
267 {
268 foreach ($objectStorage as $object) {
269 $this->attach($object, $objectStorage->getInfo());
270 }
271 }
272
273 /**
274 * Removes objects contained in another storage from the current storage.
275 *
276 * @param ObjectStorage $objectStorage The storage containing the elements to remove.
277 * @return void
278 */
279 public function removeAll(ObjectStorage $objectStorage)
280 {
281 foreach ($objectStorage as $object) {
282 $this->detach($object);
283 }
284 }
285
286 /**
287 * Returns this object storage as an array
288 *
289 * @return array The object storage
290 */
291 public function toArray()
292 {
293 $array = [];
294 $storage = array_values($this->storage);
295 foreach ($storage as $item) {
296 $array[] = $item['obj'];
297 }
298 return $array;
299 }
300
301 /**
302 * Alias of toArray which allows that method to be used from contexts which support
303 * for example dotted paths, e.g. ObjectAccess::getPropertyPath($object, 'children.array.123')
304 * to get exactly the 123rd item in the "children" property which is an ObjectStorage.
305 *
306 * @return array
307 */
308 public function getArray()
309 {
310 return $this->toArray();
311 }
312
313 /**
314 * Dummy method to avoid serialization.
315 *
316 * @throws \RuntimeException
317 * @return void
318 */
319 public function serialize()
320 {
321 throw new \RuntimeException('An ObjectStorage instance cannot be serialized.', 1267700868);
322 }
323
324 /**
325 * Dummy method to avoid unserialization.
326 *
327 * @param string $serialized
328 * @throws \RuntimeException
329 * @return void
330 */
331 public function unserialize($serialized)
332 {
333 throw new \RuntimeException('A ObjectStorage instance cannot be unserialized.', 1267700870);
334 }
335
336 /**
337 * Register the storage's clean state, e.g. after it has been reconstituted from the database.
338 *
339 * @return void
340 */
341 public function _memorizeCleanState()
342 {
343 $this->isModified = false;
344 }
345
346 /**
347 * Returns TRUE if the storage was modified after reconstitution.
348 *
349 * @return bool
350 */
351 public function _isDirty()
352 {
353 return $this->isModified;
354 }
355
356 /**
357 * Returns TRUE if an object is added, then removed and added at a different position
358 *
359 * @param mixed $object
360 * @return bool
361 */
362 public function isRelationDirty($object)
363 {
364 return (isset($this->addedObjectsPositions[spl_object_hash($object)])
365 && isset($this->removedObjectsPositions[spl_object_hash($object)])
366 && ($this->addedObjectsPositions[spl_object_hash($object)] !== $this->removedObjectsPositions[spl_object_hash($object)]));
367 }
368
369 /**
370 * @param mixed $object
371 * @return int|NULL
372 */
373 public function getPosition($object)
374 {
375 if (!isset($this->addedObjectsPositions[spl_object_hash($object)])) {
376 return null;
377 }
378
379 return $this->addedObjectsPositions[spl_object_hash($object)];
380 }
381 }