Completed
Branch Gutenberg/master (eed722)
by
unknown
78:41 queued 66:19
created

Collection::hasObjects()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
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
    /**
31
     * an interface (or class) name to be used for restricting the type of objects added to the storage
32
     * this should be set from within the child class constructor
33
     *
34
     * @type string $interface
35
     */
36
    protected $collection_interface;
37
38
39
    /**
40
     * Collection constructor
41
     *
42
     * @param string $collection_interface
43
     * @throws InvalidInterfaceException
44
     */
45
    public function __construct($collection_interface)
46
    {
47
        $this->setCollectionInterface($collection_interface);
48
        $this->setCollectionIdentifier();
49
    }
50
51
52
    /**
53
     * @return string
54
     */
55
    public function collectionIdentifier()
56
    {
57
        return $this->collection_identifier;
58
    }
59
60
61
    /**
62
     * creates a very readable unique 9 character identifier like:  CF2-532-DAC
63
     * and appends it to the non-qualified class name, ex: ThingCollection-CF2-532-DAC
64
     *
65
     * @return void
66
     */
67
    protected function setCollectionIdentifier()
68
    {
69
        // hash a few collection details
70
        $identifier = md5(spl_object_hash($this) . $this->collection_interface . time());
71
        // grab a few characters from the start, middle, and end of the hash
72
        $id = array();
73
        for ($x = 0; $x < 19; $x += 9) {
74
            $id[] = substr($identifier, $x, 3);
75
        }
76
        $identifier = basename(str_replace('\\', '/', get_class($this)));
77
        $identifier .= '-' . strtoupper(implode('-', $id));
78
        $this->collection_identifier = $identifier;
79
    }
80
81
82
    /**
83
     * setCollectionInterface
84
     *
85
     * @param  string $collection_interface
86
     * @throws 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
     * @param        $object
104
     * @param  mixed $identifier
105
     * @return bool
106
     * @throws InvalidEntityException
107
     * @throws DuplicateCollectionIdentifierException
108
     */
109
    public function add($object, $identifier = null)
110
    {
111
        if (! $object instanceof $this->collection_interface) {
112
            throw new InvalidEntityException($object, $this->collection_interface);
113
        }
114
        if ($this->contains($object)) {
115
            throw new DuplicateCollectionIdentifierException($identifier);
116
        }
117
        $this->attach($object);
118
        $this->setIdentifier($object, $identifier);
119
        return $this->contains($object);
120
    }
121
122
123
    /**
124
     * setIdentifier
125
     * Sets the data associated with an object in the Collection
126
     * if no $identifier is supplied, then the spl_object_hash() is used
127
     *
128
     * @access public
129
     * @param        $object
130
     * @param  mixed $identifier
131
     * @return bool
132
     */
133 View Code Duplication
    public function setIdentifier($object, $identifier = null)
134
    {
135
        $identifier = ! empty($identifier)
136
            ? $identifier
137
            : spl_object_hash($object);
138
        $this->rewind();
139
        while ($this->valid()) {
140
            if ($object === $this->current()) {
141
                $this->setInfo($identifier);
142
                $this->rewind();
143
                return true;
144
            }
145
            $this->next();
146
        }
147
        return false;
148
    }
149
150
151
    /**
152
     * get
153
     * finds and returns an object in the Collection based on the identifier that was set using addObject()
154
     * PLZ NOTE: the pointer is reset to the beginning of the collection before returning
155
     *
156
     * @access public
157
     * @param mixed $identifier
158
     * @return mixed
159
     */
160 View Code Duplication
    public function get($identifier)
161
    {
162
        $this->rewind();
163
        while ($this->valid()) {
164
            if ($identifier === $this->getInfo()) {
165
                $object = $this->current();
166
                $this->rewind();
167
                return $object;
168
            }
169
            $this->next();
170
        }
171
        return null;
172
    }
173
174
175
    /**
176
     * has
177
     * returns TRUE or FALSE
178
     * depending on whether the object is within the Collection
179
     * based on the supplied $identifier
180
     *
181
     * @access public
182
     * @param  mixed $identifier
183
     * @return bool
184
     */
185 View Code Duplication
    public function has($identifier)
186
    {
187
        $this->rewind();
188
        while ($this->valid()) {
189
            if ($identifier === $this->getInfo()) {
190
                $this->rewind();
191
                return true;
192
            }
193
            $this->next();
194
        }
195
        return false;
196
    }
197
198
199
    /**
200
     * hasObject
201
     * returns TRUE or FALSE depending on whether the supplied object is within the Collection
202
     *
203
     * @access public
204
     * @param $object
205
     * @return bool
206
     */
207
    public function hasObject($object)
208
    {
209
        return $this->contains($object);
210
    }
211
212
213
    /**
214
     * hasObjects
215
     * returns true if there are objects within the Collection, and false if it is empty
216
     *
217
     * @access public
218
     * @return bool
219
     */
220
    public function hasObjects()
221
    {
222
        return $this->count() !== 0;
223
    }
224
225
226
    /**
227
     * isEmpty
228
     * returns true if there are no objects within the Collection, and false if there are
229
     *
230
     * @access public
231
     * @return bool
232
     */
233
    public function isEmpty()
234
    {
235
        return $this->count() === 0;
236
    }
237
238
239
    /**
240
     * remove
241
     * detaches an object from the Collection
242
     *
243
     * @access public
244
     * @param $object
245
     * @return bool
246
     */
247
    public function remove($object)
248
    {
249
        $this->detach($object);
250
        return true;
251
    }
252
253
254
    /**
255
     * setCurrent
256
     * advances pointer to the object whose identifier matches that which was provided
257
     *
258
     * @access public
259
     * @param mixed $identifier
260
     * @return boolean
261
     */
262 View Code Duplication
    public function setCurrent($identifier)
263
    {
264
        $this->rewind();
265
        while ($this->valid()) {
266
            if ($identifier === $this->getInfo()) {
267
                return true;
268
            }
269
            $this->next();
270
        }
271
        return false;
272
    }
273
274
275
    /**
276
     * setCurrentUsingObject
277
     * advances pointer to the provided object
278
     *
279
     * @access public
280
     * @param $object
281
     * @return boolean
282
     */
283
    public function setCurrentUsingObject($object)
284
    {
285
        $this->rewind();
286
        while ($this->valid()) {
287
            if ($this->current() === $object) {
288
                return true;
289
            }
290
            $this->next();
291
        }
292
        return false;
293
    }
294
295
296
    /**
297
     * Returns the object occupying the index before the current object,
298
     * unless this is already the first object, in which case it just returns the first object
299
     *
300
     * @return mixed
301
     */
302
    public function previous()
303
    {
304
        $index = $this->indexOf($this->current());
305
        if ($index === 0) {
306
            return $this->current();
307
        }
308
        $index--;
309
        return $this->objectAtIndex($index);
310
    }
311
312
313
    /**
314
     * Returns the index of a given object, or false if not found
315
     *
316
     * @see http://stackoverflow.com/a/8736013
317
     * @param $object
318
     * @return boolean|int|string
319
     */
320
    public function indexOf($object)
321
    {
322
        if (! $this->contains($object)) {
323
            return false;
324
        }
325
        foreach ($this as $index => $obj) {
326
            if ($obj === $object) {
327
                return $index;
328
            }
329
        }
330
        return false;
331
    }
332
333
334
    /**
335
     * Returns the object at the given index
336
     *
337
     * @see http://stackoverflow.com/a/8736013
338
     * @param int $index
339
     * @return mixed
340
     */
341
    public function objectAtIndex($index)
342
    {
343
        $iterator = new LimitIterator($this, $index, 1);
344
        $iterator->rewind();
345
        return $iterator->current();
346
    }
347
348
349
    /**
350
     * Returns the sequence of objects as specified by the offset and length
351
     *
352
     * @see http://stackoverflow.com/a/8736013
353
     * @param int $offset
354
     * @param int $length
355
     * @return array
356
     */
357
    public function slice($offset, $length)
358
    {
359
        $slice = array();
360
        $iterator = new LimitIterator($this, $offset, $length);
361
        foreach ($iterator as $object) {
362
            $slice[] = $object;
363
        }
364
        return $slice;
365
    }
366
367
368
    /**
369
     * Inserts an object at a certain point
370
     *
371
     * @see http://stackoverflow.com/a/8736013
372
     * @param mixed $object A single object
373
     * @param int   $index
374
     * @param mixed $identifier
375
     * @return bool
376
     * @throws DuplicateCollectionIdentifierException
377
     * @throws InvalidEntityException
378
     */
379
    public function insertObjectAt($object, $index, $identifier = null)
380
    {
381
        // check to ensure that objects don't already exist in the collection
382
        if ($this->has($identifier)) {
383
            throw new DuplicateCollectionIdentifierException($identifier);
384
        }
385
        // detach any objects at or past this index
386
        $remaining_objects = array();
387
        if ($index < $this->count()) {
388
            $remaining_objects = $this->slice($index, $this->count() - $index);
389
            foreach ($remaining_objects as $key => $remaining_object) {
390
                // we need to grab the identifiers for each object and use them as keys
391
                $remaining_objects[ $remaining_object->getInfo() ] = $remaining_object;
392
                // and then remove the object from the current tracking array
393
                unset($remaining_objects[ $key ]);
394
                // and then remove it from the Collection
395
                $this->detach($remaining_object);
396
            }
397
        }
398
        // add the new object we're splicing in
399
        $this->add($object, $identifier);
400
        // attach the objects we previously detached
401
        foreach ($remaining_objects as $key => $remaining_object) {
402
            $this->add($remaining_object, $key);
403
        }
404
        return $this->contains($object);
405
    }
406
407
408
    /**
409
     * Inserts an object (or an array of objects) at a certain point
410
     *
411
     * @see http://stackoverflow.com/a/8736013
412
     * @param mixed $objects A single object or an array of objects
413
     * @param int   $index
414
     */
415
    public function insertAt($objects, $index)
416
    {
417
        if (! is_array($objects)) {
418
            $objects = array($objects);
419
        }
420
        // check to ensure that objects don't already exist in the collection
421
        foreach ($objects as $key => $object) {
422
            if ($this->contains($object)) {
423
                unset($objects[ $key ]);
424
            }
425
        }
426
        // do we have any objects left?
427
        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...
428
            return;
429
        }
430
        // detach any objects at or past this index
431
        $remaining = array();
432
        if ($index < $this->count()) {
433
            $remaining = $this->slice($index, $this->count() - $index);
434
            foreach ($remaining as $object) {
435
                $this->detach($object);
436
            }
437
        }
438
        // add the new objects we're splicing in
439
        foreach ($objects as $object) {
440
            $this->attach($object);
441
        }
442
        // attach the objects we previously detached
443
        foreach ($remaining as $object) {
444
            $this->attach($object);
445
        }
446
    }
447
448
449
    /**
450
     * Removes the object at the given index
451
     *
452
     * @see http://stackoverflow.com/a/8736013
453
     * @param int $index
454
     */
455
    public function removeAt($index)
456
    {
457
        $this->detach($this->objectAtIndex($index));
458
    }
459
460
461
    /**
462
     * detaches ALL objects from the Collection
463
     */
464
    public function detachAll()
465
    {
466
        $this->rewind();
467
        while ($this->valid()) {
468
            $object = $this->current();
469
            $this->next();
470
            $this->detach($object);
471
        }
472
    }
473
474
475
    /**
476
     * unsets and detaches ALL objects from the Collection
477
     */
478
    public function trashAndDetachAll()
479
    {
480
        $this->rewind();
481
        while ($this->valid()) {
482
            $object = $this->current();
483
            $this->next();
484
            $this->detach($object);
485
            unset($object);
486
        }
487
    }
488
}
489