Completed
Push — 1.x ( aec408...aa79b5 )
by Pol
02:33 queued 54s
created

Attributes   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 399
Duplicated Lines 7.77 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 98.7%

Importance

Changes 0
Metric Value
wmc 59
lcom 1
cbo 1
dl 31
loc 399
ccs 152
cts 154
cp 0.987
rs 4.08
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A offsetGet() 0 10 2
A delete() 0 8 2
A __construct() 0 9 2
A offsetSet() 0 7 1
A offsetUnset() 0 4 1
A offsetExists() 0 4 1
A remove() 0 12 3
A replace() 0 17 4
A exists() 0 12 3
A contains() 0 8 2
A __toString() 0 4 1
A render() 0 6 2
A toArray() 0 17 3
A getStorage() 0 4 1
A getIterator() 0 4 1
A count() 0 4 1
A prepareValues() 0 31 4
A normalizeValue() 0 4 1
B ensureFlatArray() 0 45 10
B ensureString() 0 30 9
A append() 15 15 2
A merge() 16 16 2
A without() 0 6 1

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 Attributes 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 Attributes, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace drupol\htmltag;
4
5
/**
6
 * Class Attributes.
7
 */
8
class Attributes implements AttributesInterface
9
{
10
    /**
11
     * Stores the attribute data.
12
     *
13
     * @var \drupol\htmltag\Attribute[]
14
     */
15
    private $storage = array();
16
17
    /**
18
     * {@inheritdoc}
19
     */
20 24
    public function __construct(array $attributes = array())
21
    {
22 24
        foreach ($attributes as $name => $value) {
23 2
            $this->storage[$name] = new Attribute(
24 2
                $name,
25 2
                $this->ensureString($value)
26
            );
27
        }
28 24
    }
29
30
    /**
31
     * {@inheritdoc}
32
     */
33 2
    public function offsetGet($name)
34
    {
35 2
        if (!isset($this->storage[$name])) {
36 2
            $this->storage[$name] = new Attribute(
37 2
                $name
38
            );
39
        }
40
41 2
        return $this->storage[$name];
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47 1
    public function offsetSet($name, $value = null)
48
    {
49 1
        $this->storage[$name] = new Attribute(
50 1
            $name,
51 1
            $this->ensureString($value)
52
        );
53 1
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58 1
    public function offsetUnset($name)
59
    {
60 1
        unset($this->storage[$name]);
61 1
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 2
    public function offsetExists($name)
67
    {
68 2
        return isset($this->storage[$name]);
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 14 View Code Duplication
    public function append($key, $value = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
75
    {
76
        $this->storage += array(
77 14
            $key => new Attribute(
78 14
                $key,
79 14
                $this->ensureString($value)
80
            )
81
        );
82
83 14
        foreach ($this->normalizeValue($value) as $value_item) {
84 14
            $this->storage[$key]->append($value_item);
85
        }
86
87 14
        return $this;
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93 2
    public function remove($key, $value = '')
94
    {
95 2
        if (!isset($this->storage[$key])) {
96 1
            return $this;
97
        }
98
99 1
        foreach ($this->normalizeValue($value) as $value_item) {
100 1
            $this->storage[$key]->remove($value_item);
101
        }
102
103 1
        return $this;
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 2
    public function delete($name = array())
110
    {
111 2
        foreach ($this->normalizeValue($name) as $attribute_name) {
112 2
            unset($this->storage[$attribute_name]);
113
        }
114
115 2
        return $this;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 1
    public function without($key)
122
    {
123 1
        $attributes = clone $this;
124
125 1
        return $attributes->delete($key);
0 ignored issues
show
Documentation introduced by
$key is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131 1
    public function replace($key, $value, $replacement)
132
    {
133 1
        if (!isset($this->storage[$key])) {
134 1
            return $this;
135
        }
136
137 1
        if (!$this->contains($key, $value)) {
138 1
            return $this;
139
        }
140
141 1
        $this->storage[$key]->remove($value);
142 1
        foreach ($this->normalizeValue($replacement) as $replacement_value) {
143 1
            $this->storage[$key]->append($replacement_value);
144
        }
145
146 1
        return $this;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 1 View Code Duplication
    public function merge(array $data = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
153
    {
154 1
        foreach ($data as $key => $value) {
155
            $this->storage += array(
156 1
                $key => new Attribute(
157 1
                    $key
158
                )
159
            );
160
161 1
            $this->storage[$key]->merge(
162 1
                $this->normalizeValue($value)
163
            );
164
        }
165
166 1
        return $this;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172 1
    public function exists($key, $value = null)
173
    {
174 1
        if (!isset($this->storage[$key])) {
175 1
            return false;
176
        }
177
178 1
        if (null !== $value) {
179 1
            return $this->contains($key, $value);
180
        }
181
182 1
        return true;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188 3
    public function contains($key, $value)
189
    {
190 3
        if (!isset($this->storage[$key])) {
191 1
            return false;
192
        }
193
194 3
        return $this->storage[$key]->contains($value);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 7
    public function __toString()
201
    {
202 7
        return $this->render();
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208 13
    public function render()
209
    {
210 13
        $attributes = implode(' ', $this->prepareValues());
211
212 13
        return $attributes ? ' ' . $attributes : '';
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218 2
    public function toArray()
219
    {
220 2
        $attributes = $this->storage;
221
222
        // If empty, just return an empty array.
223 2
        if (empty($attributes)) {
224 2
            return array();
225
        }
226
227 1
        $result = [];
228
229 1
        foreach ($this->prepareValues() as $attribute) {
230 1
            $result[$attribute->getName()] = $attribute->getValueAsArray();
231
        }
232
233 1
        return $result;
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239 1
    public function getStorage()
240
    {
241 1
        return $this->storage;
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247 1
    public function getIterator()
248
    {
249 1
        return new \ArrayIterator($this->toArray());
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 1
    public function count()
256
    {
257 1
        return count($this->storage);
258
    }
259
260
    /**
261
     * Returns all storage elements as an array.
262
     *
263
     * @return \drupol\htmltag\Attribute[]
264
     *   An associative array of attributes.
265
     */
266 14
    private function prepareValues()
267
    {
268 14
        $attributes = $this->storage;
269
270
        // If empty, just return an empty array.
271 14
        if (empty($attributes)) {
272 5
            return array();
273
        }
274
275
        // Sort the attributes.
276 12
        ksort($attributes);
277
278 12
        $result = [];
279
280 12
        foreach ($attributes as $attribute_name => $attribute) {
281
            switch ($attribute_name) {
282 12
                case 'class':
283 10
                    $classes = $attribute->getValueAsArray();
284 10
                    asort($classes);
285 10
                    $result[$attribute->getName()] = $attribute->set(
286 10
                        implode(' ', $classes)
287
                    );
288 10
                    break;
289
290
                default:
291 12
                    $result[$attribute->getName()] = $attribute;
292
            }
293
        }
294
295 12
        return $result;
296
    }
297
298
    /**
299
     * Normalize a value.
300
     *
301
     * @param mixed $value
302
     *  The value to normalize.
303
     *
304
     * @return array
305
     *   The value normalized.
306
     */
307 16
    private function normalizeValue($value)
308
    {
309 16
        return $this->ensureFlatArray($value);
310
    }
311
312
    /**
313
     * Todo.
314
     *
315
     * @param mixed $value
316
     *   Todo.
317
     *
318
     * @return array
319
     *   The array, flattened.
320
     */
321 16
    private function ensureFlatArray($value)
322
    {
323 16
        $type = gettype($value);
324
325 16
        $return = array();
326
327
        switch ($type) {
328 16
            case 'string':
329 16
                $return = explode(
330 16
                    ' ',
331 16
                    $this->ensureString($value)
332
                );
333 16
                break;
334
335 5
            case 'array':
336 4
                $flat_array = iterator_to_array(
337 4
                    new \RecursiveIteratorIterator(
338 4
                        new \RecursiveArrayIterator(
339 4
                            $value
340
                        )
341
                    ),
342 4
                    false
343
                );
344
345 4
                $return = [];
346 4
                foreach ($flat_array as $item) {
347 4
                    $return = array_merge(
348 4
                        $return,
349 4
                        $this->normalizeValue($item)
350
                    );
351
                }
352 4
                break;
353
354 2
            case 'double':
355 2
            case 'integer':
356 1
                $return = array($value);
357 1
                break;
358 1
            case 'object':
359 1
            case 'boolean':
360 1
            case 'resource':
361 1
            case 'NULL':
362
        }
363
364 16
        return $return;
365
    }
366
367
    /**
368
     * Todo.
369
     *
370
     * @param mixed $value
371
     *   Todo.
372
     *
373
     * @return string
374
     *   A string.
375
     */
376 16
    private function ensureString($value)
377
    {
378 16
        $type = gettype($value);
379
380 16
        $return = '';
381
382
        switch ($type) {
383 16
            case 'string':
384 16
                $return = $value;
385 16
                break;
386
387 4
            case 'array':
388 3
                $return = implode(
389 3
                    ' ',
390 3
                    $this->ensureFlatArray($value)
391
                );
392 3
                break;
393
394 3
            case 'double':
395 3
            case 'integer':
396 1
                $return = (string) $value;
397 1
                break;
398 2
            case 'object':
399 2
            case 'boolean':
400 2
            case 'resource':
401 2
            case 'NULL':
402
        }
403
404 16
        return $return;
405
    }
406
}
407