Completed
Branch GDPR/user-data-export (1838dd)
by
unknown
53:17 queued 40:13
created

Collection::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 1
b 0
f 1
1
<?php
2
3
namespace EventEspresso\core\services\collections;
4
5
use EventEspresso\core\exceptions\InvalidEntityException;
6
use EventEspresso\core\exceptions\InvalidInterfaceException;
7
use LimitIterator;
8
use SplObjectStorage;
9
10
/**
11
 * Class Collection
12
 * class for managing a set of entities that all adhere to the same interface
13
 * unofficially follows Interop\Container\ContainerInterface
14
 *
15
 * @package       Event Espresso
16
 * @author        Brent Christensen
17
 * @since         4.9.0
18
 */
19
class Collection extends SplObjectStorage implements CollectionInterface
20
{
21
22
    /**
23
     * a unique string for identifying this collection
24
     *
25
     * @type string $collection_identifier
26
     */
27
    protected $collection_identifier;
28
29
    /**
30
     * an interface (or class) name to be used for restricting the type of objects added to the storage
31
     * this should be set from within the child class constructor
32
     *
33
     * @type string $interface
34
     */
35
    protected $collection_interface;
36
37
38
    /**
39
     * Collection constructor
40
     *
41
     * @param string $collection_interface
42
     * @throws InvalidInterfaceException
43
     */
44
    public function __construct($collection_interface)
45
    {
46
        $this->setCollectionInterface($collection_interface);
47
        $this->setCollectionIdentifier();
48
    }
49
50
51
    /**
52
     * @return string
53
     */
54
    public function collectionIdentifier()
55
    {
56
        return $this->collection_identifier;
57
    }
58
59
60
    /**
61
     * creates a very readable unique 9 character identifier like:  CF2-532-DAC
62
     * and appends it to the non-qualified class name, ex: ThingCollection-CF2-532-DAC
63
     *
64
     * @return void
65
     */
66
    protected function setCollectionIdentifier()
67
    {
68
        // hash a few collection details
69
        $identifier = md5(spl_object_hash($this) . $this->collection_interface . time());
70
        // grab a few characters from the start, middle, and end of the hash
71
        $id = array();
72
        for ($x = 0; $x < 19; $x += 9) {
73
            $id[] = substr($identifier, $x, 3);
74
        }
75
        $identifier = basename(str_replace('\\', '/', get_class($this)));
76
        $identifier .= '-' . strtoupper(implode('-', $id));
77
        $this->collection_identifier = $identifier;
78
    }
79
80
81
    /**
82
     * setCollectionInterface
83
     *
84
     * @access protected
85
     * @param  string $collection_interface
86
     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
87
     */
88 View Code Duplication
    protected function setCollectionInterface($collection_interface)
89
    {
90
        if (! (interface_exists($collection_interface) || class_exists($collection_interface))) {
91
            throw new InvalidInterfaceException($collection_interface);
92
        }
93
        $this->collection_interface = $collection_interface;
94
    }
95
96
97
    /**
98
     * add
99
     * attaches an object to the Collection
100
     * and sets any supplied data associated with the current iterator entry
101
     * by calling EE_Object_Collection::set_identifier()
102
     *
103
     * @access public
104
     * @param        $object
105
     * @param  mixed $identifier
106
     * @return bool
107
     * @throws InvalidEntityException
108
     * @throws DuplicateCollectionIdentifierException
109
     */
110
    public function add($object, $identifier = null)
111
    {
112
        if (! $object instanceof $this->collection_interface) {
113
            throw new InvalidEntityException($object, $this->collection_interface);
114
        }
115
        if ($this->contains($object)) {
116
            throw new DuplicateCollectionIdentifierException($identifier);
117
        }
118
        $this->attach($object);
119
        $this->setIdentifier($object, $identifier);
120
        return $this->contains($object);
121
    }
122
123
124
    /**
125
     * setIdentifier
126
     * Sets the data associated with an object in the Collection
127
     * if no $identifier is supplied, then the spl_object_hash() is used
128
     *
129
     * @access public
130
     * @param        $object
131
     * @param  mixed $identifier
132
     * @return bool
133
     */
134 View Code Duplication
    public function setIdentifier($object, $identifier = null)
135
    {
136
        $identifier = ! empty($identifier)
137
            ? $identifier
138
            : spl_object_hash($object);
139
        $this->rewind();
140
        while ($this->valid()) {
141
            if ($object === $this->current()) {
142
                $this->setInfo($identifier);
143
                $this->rewind();
144
                return true;
145
            }
146
            $this->next();
147
        }
148
        return false;
149
    }
150
151
152
    /**
153
     * get
154
     * finds and returns an object in the Collection based on the identifier that was set using addObject()
155
     * PLZ NOTE: the pointer is reset to the beginning of the collection before returning
156
     *
157
     * @access public
158
     * @param mixed $identifier
159
     * @return mixed
160
     */
161 View Code Duplication
    public function get($identifier)
162
    {
163
        $this->rewind();
164
        while ($this->valid()) {
165
            if ($identifier === $this->getInfo()) {
166
                $object = $this->current();
167
                $this->rewind();
168
                return $object;
169
            }
170
            $this->next();
171
        }
172
        return null;
173
    }
174
175
176
    /**
177
     * has
178
     * returns TRUE or FALSE
179
     * depending on whether the object is within the Collection
180
     * based on the supplied $identifier
181
     *
182
     * @access public
183
     * @param  mixed $identifier
184
     * @return bool
185
     */
186 View Code Duplication
    public function has($identifier)
187
    {
188
        $this->rewind();
189
        while ($this->valid()) {
190
            if ($identifier === $this->getInfo()) {
191
                $this->rewind();
192
                return true;
193
            }
194
            $this->next();
195
        }
196
        return false;
197
    }
198
199
200
    /**
201
     * hasObject
202
     * returns TRUE or FALSE depending on whether the supplied object is within the Collection
203
     *
204
     * @access public
205
     * @param $object
206
     * @return bool
207
     */
208
    public function hasObject($object)
209
    {
210
        return $this->contains($object);
211
    }
212
213
214
    /**
215
     * hasObjects
216
     * returns true if there are objects within the Collection, and false if it is empty
217
     *
218
     * @access public
219
     * @return bool
220
     */
221
    public function hasObjects()
222
    {
223
        return $this->count() !== 0;
224
    }
225
226
227
    /**
228
     * isEmpty
229
     * returns true if there are no objects within the Collection, and false if there are
230
     *
231
     * @access public
232
     * @return bool
233
     */
234
    public function isEmpty()
235
    {
236
        return $this->count() === 0;
237
    }
238
239
240
    /**
241
     * remove
242
     * detaches an object from the Collection
243
     *
244
     * @access public
245
     * @param $object
246
     * @return bool
247
     */
248
    public function remove($object)
249
    {
250
        $this->detach($object);
251
        return true;
252
    }
253
254
255
    /**
256
     * setCurrent
257
     * advances pointer to the object whose identifier matches that which was provided
258
     *
259
     * @access public
260
     * @param mixed $identifier
261
     * @return boolean
262
     */
263 View Code Duplication
    public function setCurrent($identifier)
264
    {
265
        $this->rewind();
266
        while ($this->valid()) {
267
            if ($identifier === $this->getInfo()) {
268
                return true;
269
            }
270
            $this->next();
271
        }
272
        return false;
273
    }
274
275
276
    /**
277
     * setCurrentUsingObject
278
     * advances pointer to the provided object
279
     *
280
     * @access public
281
     * @param $object
282
     * @return boolean
283
     */
284
    public function setCurrentUsingObject($object)
285
    {
286
        $this->rewind();
287
        while ($this->valid()) {
288
            if ($this->current() === $object) {
289
                return true;
290
            }
291
            $this->next();
292
        }
293
        return false;
294
    }
295
296
297
    /**
298
     * Returns the object occupying the index before the current object,
299
     * unless this is already the first object, in which case it just returns the first object
300
     *
301
     * @return mixed
302
     */
303
    public function previous()
304
    {
305
        $index = $this->indexOf($this->current());
306
        if ($index === 0) {
307
            return $this->current();
308
        }
309
        $index--;
310
        return $this->objectAtIndex($index);
311
    }
312
313
314
    /**
315
     * Returns the index of a given object, or false if not found
316
     *
317
     * @see http://stackoverflow.com/a/8736013
318
     * @param $object
319
     * @return boolean|int|string
320
     */
321
    public function indexOf($object)
322
    {
323
        if (! $this->contains($object)) {
324
            return false;
325
        }
326
        foreach ($this as $index => $obj) {
327
            if ($obj === $object) {
328
                return $index;
329
            }
330
        }
331
        return false;
332
    }
333
334
335
    /**
336
     * Returns the object at the given index
337
     *
338
     * @see http://stackoverflow.com/a/8736013
339
     * @param int $index
340
     * @return mixed
341
     */
342
    public function objectAtIndex($index)
343
    {
344
        $iterator = new LimitIterator($this, $index, 1);
345
        $iterator->rewind();
346
        return $iterator->current();
347
    }
348
349
350
    /**
351
     * Returns the sequence of objects as specified by the offset and length
352
     *
353
     * @see http://stackoverflow.com/a/8736013
354
     * @param int $offset
355
     * @param int $length
356
     * @return array
357
     */
358
    public function slice($offset, $length)
359
    {
360
        $slice = array();
361
        $iterator = new LimitIterator($this, $offset, $length);
362
        foreach ($iterator as $object) {
363
            $slice[] = $object;
364
        }
365
        return $slice;
366
    }
367
368
369
    /**
370
     * Inserts an object at a certain point
371
     *
372
     * @see http://stackoverflow.com/a/8736013
373
     * @param mixed $object A single object
374
     * @param int   $index
375
     * @param mixed $identifier
376
     * @return bool
377
     * @throws DuplicateCollectionIdentifierException
378
     * @throws InvalidEntityException
379
     */
380
    public function insertObjectAt($object, $index, $identifier = null)
381
    {
382
        // check to ensure that objects don't already exist in the collection
383
        if ($this->has($identifier)) {
384
            throw new DuplicateCollectionIdentifierException($identifier);
385
        }
386
        // detach any objects at or past this index
387
        $remaining_objects = array();
388
        if ($index < $this->count()) {
389
            $remaining_objects = $this->slice($index, $this->count() - $index);
390
            foreach ($remaining_objects as $key => $remaining_object) {
391
                // we need to grab the identifiers for each object and use them as keys
392
                $remaining_objects[ $remaining_object->getInfo() ] = $remaining_object;
393
                // and then remove the object from the current tracking array
394
                unset($remaining_objects[ $key ]);
395
                // and then remove it from the Collection
396
                $this->detach($remaining_object);
397
            }
398
        }
399
        // add the new object we're splicing in
400
        $this->add($object, $identifier);
401
        // attach the objects we previously detached
402
        foreach ($remaining_objects as $key => $remaining_object) {
403
            $this->add($remaining_object, $key);
404
        }
405
        return $this->contains($object);
406
    }
407
408
409
    /**
410
     * Inserts an object (or an array of objects) at a certain point
411
     *
412
     * @see http://stackoverflow.com/a/8736013
413
     * @param mixed $objects A single object or an array of objects
414
     * @param int   $index
415
     */
416
    public function insertAt($objects, $index)
417
    {
418
        if (! is_array($objects)) {
419
            $objects = array($objects);
420
        }
421
        // check to ensure that objects don't already exist in the collection
422
        foreach ($objects as $key => $object) {
423
            if ($this->contains($object)) {
424
                unset($objects[ $key ]);
425
            }
426
        }
427
        // do we have any objects left?
428
        if (! $objects) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $objects of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
429
            return;
430
        }
431
        // detach any objects at or past this index
432
        $remaining = array();
433
        if ($index < $this->count()) {
434
            $remaining = $this->slice($index, $this->count() - $index);
435
            foreach ($remaining as $object) {
436
                $this->detach($object);
437
            }
438
        }
439
        // add the new objects we're splicing in
440
        foreach ($objects as $object) {
441
            $this->attach($object);
442
        }
443
        // attach the objects we previously detached
444
        foreach ($remaining as $object) {
445
            $this->attach($object);
446
        }
447
    }
448
449
450
    /**
451
     * Removes the object at the given index
452
     *
453
     * @see http://stackoverflow.com/a/8736013
454
     * @param int $index
455
     */
456
    public function removeAt($index)
457
    {
458
        $this->detach($this->objectAtIndex($index));
459
    }
460
461
462
    /**
463
     * detaches ALL objects from the Collection
464
     */
465
    public function detachAll()
466
    {
467
        $this->rewind();
468
        while ($this->valid()) {
469
            $object = $this->current();
470
            $this->next();
471
            $this->detach($object);
472
        }
473
    }
474
475
476
    /**
477
     * unsets and detaches ALL objects from the Collection
478
     */
479
    public function trashAndDetachAll()
480
    {
481
        $this->rewind();
482
        while ($this->valid()) {
483
            $object = $this->current();
484
            $this->next();
485
            $this->detach($object);
486
            unset($object);
487
        }
488
    }
489
}
490