Completed
Branch BUG/invalid-field-count (a0252b)
by
unknown
14:53 queued 56s
created

Collection   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 388
Duplicated Lines 13.66 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 53
loc 388
rs 8.5454
c 0
b 0
f 0
wmc 49
lcom 1
cbo 2

20 Methods

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