Completed
Branch BUG/reg-status-change-recursio... (2db0c9)
by
unknown
20:03 queued 10:32
created

Collection   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 504
Duplicated Lines 7.94 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 40
loc 504
rs 3.44
c 0
b 0
f 0
wmc 62
lcom 1
cbo 3

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setCollectionInterface() 7 7 3
A collectionName() 0 4 1
A setCollectionName() 0 6 2
A collectionIdentifier() 0 4 1
A setCollectionIdentifier() 0 11 2
A add() 0 12 3
A getIdentifier() 0 6 2
A setIdentifier() 0 14 3
A get() 12 13 3
A has() 11 12 3
A hasObject() 0 4 1
A hasObjects() 0 4 1
A isEmpty() 0 4 1
A remove() 0 5 1
A setCurrent() 10 11 3
A setCurrentUsingObject() 0 11 3
A previous() 0 9 2
A indexOf() 0 12 4
A objectAtIndex() 0 6 1
A slice() 0 9 2
A insertObjectAt() 0 27 5
B insertAt() 0 32 9
A removeAt() 0 4 1
A detachAll() 0 9 2
A trashAndDetachAll() 0 10 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Collection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Collection, and based on these observations, apply Extract Interface, too.

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