Collection   B
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 81.58%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 0
dl 0
loc 271
ccs 31
cts 38
cp 0.8158
rs 8.2769
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A clear() 0 4 1
A count() 0 4 1
A exists() 0 4 1
A checkItemInstanceOf() 0 6 2
A addItem() 0 10 2
A addItems() 0 6 2
A remove() 0 7 2
A keys() 0 4 1
A values() 0 4 1
A value() 0 10 3
A keyValues() 0 4 1
A getIterator() 0 4 1
A isItemInstanceOf() 0 13 4
B setItemsClassPolicy() 0 17 5
A getItemsClassPolicy() 0 4 1
A __isset() 0 4 1
A __get() 0 4 1
A __set() 0 4 1
A __unset() 0 4 1
A __invoke() 0 4 1
A __clone() 0 8 4
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 1

How to fix   Complexity   

Complex Class

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
namespace EngineWorks\Pivot;
3
4
/**
5
 * Simple collection class, you can use this to have a collections of members accesed by key or numeric
6
 *
7
 * The members of the collection are accesible:
8
 * - as properties: $col->member (read, write)
9
 * - as array: $col['member'] (read, write)
10
 * - per object invocation $col('member') (read)
11
 * - per method invocation $col->value('member') (read), $col->addItem($item, 'member') (write)
12
 *
13
 * Admits the isset and unset and count as alias of exists, remove and count methods
14
 * Implements the code to allow object cloning
15
 *
16
 * Contains an optional policy to ensure that items follow an specific class or interface
17
 *
18
 * The members can be inserted without key (just append the element to the end) or by a named key
19
 * if the key exists it is overwritten
20
 *
21
 * You also can extend this class to include your own rules
22
 *
23
 * As PHP does not support changing signatures, you cannot specify specific types on overrides
24
 * Try to use DocBlocks on your code to set the specific types
25
 *
26
 * @package EngineWorks\Utils
27
 *
28
 */
29
class Collection implements \IteratorAggregate, \Countable, \ArrayAccess
30
{
31
    /**
32
     * This is the internal items container
33
     * @var array
34
     */
35
    protected $items = [];
36
37
    /**
38
     * Set this property to the class name to set the item class policy
39
     * @var string
40
     */
41
    protected $itemsInstanceOf = '';
42
43
    /**
44
     * When this is TRUE an exception is thrown when access a member that does not exists
45
     * When is FALSE then it only returns NULL
46
     * @var bool
47
     */
48
    protected $throwExceptionItemNotFound = true;
49
50
    /*
51
     *
52
     * Real methods
53
     *
54
     */
55
56
    /**
57
     * Remove all elements from the collection
58
     */
59 11
    public function clear()
60
    {
61 11
        $this->items = [];
62 11
    }
63
64
    /**
65
     * Count of elements
66
     * @return int
67
     */
68 9
    public function count()
69
    {
70 9
        return count($this->items);
71
    }
72
73
    /**
74
     * Return true if the element exists (even when the content is null)
75
     * @param string|int $key
76
     * @return bool
77
     */
78 11
    public function exists($key)
79
    {
80 11
        return array_key_exists($key, $this->items);
81
    }
82
83
    /**
84
     * Throw a LogicException if an item does not follow the ItemsClass policy
85
     * @param mixed $item
86
     */
87 13
    protected function checkItemInstanceOf($item)
88
    {
89 13
        if (! $this->isItemInstanceOf($item)) {
90
            throw new \LogicException(get_class($this) . " error, item is not an instance of {$this->itemsInstanceOf}");
91
        }
92 13
    }
93
94
    /**
95
     * @param mixed $item
96
     * @param string|int $key
97
     * @return $this
98
     */
99 13
    public function addItem($item, $key = null)
100
    {
101 13
        $this->checkItemInstanceOf($item);
102 13
        if (null === $key) {
103 6
            $this->items[] = $item;
104
        } else {
105 13
            $this->items[$key] = $item;
106
        }
107 13
        return $this;
108
    }
109
110
    public function addItems($items)
111
    {
112
        foreach ($items as $key => $item) {
113
            $this->addItem($item, $key);
114
        }
115
    }
116
117
    /**
118
     * @param string|int $key
119
     * @return $this
120
     */
121
    public function remove($key)
122
    {
123
        if (array_key_exists($key, $this->items)) {
124
            unset($this->items[$key]);
125
        }
126
        return $this;
127
    }
128
129
    /**
130
     * An array only with the keys
131
     * @return array
132
     */
133
    public function keys()
134
    {
135
        return array_keys($this->items);
136
    }
137
138
    /**
139
     * An array only with the values (no keys)
140
     * @return array
141
     */
142
    public function values()
143
    {
144
        return array_values($this->items);
145
    }
146
147
    /**
148
     * An element from the collection if found, if not found it will:
149
     * If throwExceptionItemNotFound is true then it will create an exception
150
     * Otherwise will return null
151
     *
152
     * @param mixed $key
153
     * @return mixed
154
     * @throws \OutOfRangeException If protected property $throwExceptionItemNotFound is true
155
     */
156 10
    public function value($key)
157
    {
158 10
        if (! array_key_exists($key, $this->items)) {
159
            if ($this->throwExceptionItemNotFound) {
160
                throw new \OutOfRangeException("Key '$key' does not exists");
161
            }
162
            return null;
163
        }
164 10
        return $this->items[$key];
165
    }
166
167
    /**
168
     * The full array of key-values
169
     * @return array
170
     */
171
    public function keyValues()
172
    {
173
        return $this->items;
174
    }
175
176
    /**
177
     * Implementation of IteratorAggregate
178
     * @return \ArrayIterator
179
     */
180 10
    public function getIterator()
181
    {
182 10
        return new \ArrayIterator($this->items);
183
    }
184
185
    /**
186
     * Check if an item compliance with the ItemsClass policy
187
     * @param mixed $item
188
     * @return bool
189
     */
190 13
    protected function isItemInstanceOf($item)
191
    {
192 13
        if ('' === $this->itemsInstanceOf) {
193
            return true;
194
        }
195 13
        if (! is_object($item)) {
196
            return false;
197
        }
198 13
        if (! $item instanceof $this->itemsInstanceOf) {
199
            return false;
200
        }
201 13
        return true;
202
    }
203
204
    /**
205
     * Set the items class policy, if elements already exists each one is checked
206
     * @param string $classname
207
     */
208
    public function setItemsClassPolicy($classname)
209
    {
210
        if (! is_string($classname)) {
211
            throw new \InvalidArgumentException(get_class($this) . ' error, setItemsClass expects a string value');
212
        }
213
        $this->itemsInstanceOf = $classname;
214
        if ('' === $this->itemsInstanceOf) {
215
            return;  // avoid checks
216
        }
217
        foreach ($this->items as $key => $item) {
218
            if (! $this->isItemInstanceOf($item)) {
219
                throw new \LogicException(
220
                    get_class($this) . " error, the item $key is not an instance of {$this->itemsInstanceOf}"
221
                );
222
            }
223
        }
224
    }
225
226
    /**
227
     * Get the items class policy, if empty string then no policy is set
228
     * @return string
229
     */
230
    public function getItemsClassPolicy()
231
    {
232
        return $this->itemsInstanceOf;
233
    }
234
235
    /*
236
     *
237
     * Magic methods
238
     *
239
     */
240
241
    public function __isset($key)
242
    {
243
        return $this->exists($key);
244
    }
245
246
    public function __get($key)
247
    {
248
        return $this->value($key);
249
    }
250
251
    public function __set($key, $value)
252
    {
253
        $this->addItem($value, $key);
254
    }
255
256
    public function __unset($key)
257
    {
258
        $this->remove($key);
259
    }
260
261
    public function __invoke($key)
262
    {
263
        return $this->value($key);
264
    }
265
266 5
    public function __clone()
267
    {
268 5
        foreach ($this->items as $key => $item) {
269 5
            if (null !== $item && is_object($item)) {
270 5
                $this->items[$key] = clone $item;
271
            }
272
        }
273 5
    }
274
275
    /*
276
     *
277
     * ArrayAccess methods
278
     *
279
     */
280
    public function offsetExists($offset)
281
    {
282
        return $this->exists($offset);
283
    }
284
285
    public function offsetGet($offset)
286
    {
287
        return $this->value($offset);
288
    }
289
290
    public function offsetSet($offset, $value)
291
    {
292
        $this->addItem($value, $offset);
293
    }
294
295
    public function offsetUnset($offset)
296
    {
297
        $this->remove($offset);
298
    }
299
}
300