AbstractItem   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 114
c 2
b 1
f 0
dl 0
loc 217
ccs 97
cts 97
cp 1
rs 6.96
wmc 53

4 Methods

Rating   Name   Duplication   Size   Complexity  
B __call() 0 36 10
B __construct() 0 28 11
D toArray() 0 65 19
C grabData() 0 53 13

How to fix   Complexity   

Complex Class

Complex classes like AbstractItem 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.

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

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Wszetko Sitemap.
7
 *
8
 * (c) Paweł Kłopotek-Główczewski <[email protected]>
9
 *
10
 * This source file is subject to the MIT license that is bundled
11
 * with this source code in the file LICENSE.
12
 */
13
14
namespace Wszetko\Sitemap\Items;
15
16
use Error;
17
use ReflectionClass;
18
use ReflectionProperty;
19
use Wszetko\Sitemap\Interfaces\DataType;
20
use Wszetko\Sitemap\Interfaces\Item;
21
use Wszetko\Sitemap\Items\DataTypes\ArrayType;
22
use Wszetko\Sitemap\Traits\Domain;
23
use Wszetko\Sitemap\Traits\IsAssoc;
24
25
/**
26
 * Class AbstractItem.
27
 *
28
 * @package Wszetko\Sitemap\Items
29
 */
30
abstract class AbstractItem implements Item
31
{
32
    use IsAssoc;
33
    use Domain;
34
35
    /**
36
     * AbstractItem constructor.
37
     *
38
     * @throws \ReflectionException
39
     */
40 508
    public function __construct()
41
    {
42 508
        $class = new ReflectionClass($this);
43 508
        $properties = $class->getProperties(ReflectionProperty::IS_PROTECTED);
44
45 508
        foreach ($properties as $property) {
46 506
            $data = $this->grabData($property);
47
48
            if (
49 506
                is_array($data) &&
50
                (
51 506
                    isset($data['type']) &&
52 506
                    '' !== $data['type']
53
                ) &&
54 506
                class_exists($data['type']) &&
55 506
                in_array('Wszetko\Sitemap\Interfaces\DataType', class_implements($data['type']), true)
56
            ) {
57
                if (
58 506
                    isset($data['dataType']) &&
59 506
                    '' !== $data['dataType'] &&
60 506
                    class_exists($data['dataType']) &&
61 506
                    in_array('Wszetko\Sitemap\Interfaces\DataType', class_implements($data['dataType']), true)
62
                ) {
63 390
                    $this->{$property->getName()} = new ArrayType($property->getName(), $data['dataType']);
64 390
                    $this->{$property->getName()}->getBaseDataType()->addAttributes($data['attributes']);
65
                } else {
66 488
                    $this->{$property->getName()} = new $data['type']($property->getName());
67 488
                    $this->{$property->getName()}->addAttributes($data['attributes']);
68
                }
69
            }
70
        }
71 508
    }
72
73
    /**
74
     * @param mixed $name
75
     * @param mixed $arguments
76
     *
77
     * @return mixed
78
     *
79
     * @throws \Error
80
     */
81 506
    public function __call($name, $arguments)
82
    {
83 506
        $operation = mb_substr($name, 0, 3);
84 506
        $property = lcfirst(mb_substr($name, 3));
85
86
        if (
87 506
            property_exists($this, $property) &&
88 506
            in_array($operation, ['add', 'set', 'get'], true) &&
89 506
            ($this->{$property} instanceof DataType)
90
        ) {
91 506
            switch ($operation) {
92 506
                case 'add':
93 56
                    if (method_exists($this->{$property}, 'addValue')) {
94 54
                        $this->{$property}->addValue($arguments[0], array_slice($arguments, 1));
95
96 52
                        return $this;
97
                    }
98
99 2
                    break;
100 492
                case 'set':
101 488
                    $this->{$property}->setValue($arguments[0], array_slice($arguments, 1));
102
103 484
                    return $this;
104 444
                case 'get':
105
                    if (
106 444
                        method_exists($this->{$property}, 'setDomain') &&
107 444
                        null !== $this->getDomain()
108
                    ) {
109 86
                        $this->{$property}->setDomain($this->getDomain());
110
                    }
111
112 444
                    return $this->{$property}->getValue();
113
            }
114
        }
115
116 4
        throw new Error('Call to undefined method ' . __CLASS__ . '::' . $name . '()');
117
    }
118
119
    /**
120
     * @return array
121
     */
122 20
    public function toArray(): array
123
    {
124 20
        $array = [];
125
126 20
        if (static::NAMESPACE_NAME && static::ELEMENT_NAME) {
127
            $array = [
128 12
                '_namespace' => static::NAMESPACE_NAME,
129 12
                '_element' => static::ELEMENT_NAME,
130
            ];
131
        }
132
133 20
        $array[static::ELEMENT_NAME] = [];
134
135 20
        foreach (array_keys(get_object_vars($this)) as $property) {
136 20
            if (is_object($this->{$property})) {
137 18
                $method = 'get' . ucfirst($property);
138 18
                preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $property, $matches);
139 18
                $property = $matches[0];
140
141 18
                foreach ($property as &$match) {
142 18
                    $match = $match == mb_strtoupper($match) ? mb_strtolower($match) : lcfirst($match);
143
                }
144
145 18
                $property = implode('_', $property);
146 18
                $data = $this->{$method}();
147
148 18
                if (is_array($data)) {
149 4
                    if ($this->isAssoc($data)) {
150 4
                        $item = array_key_first($data);
151
152 4
                        if (null !== $item) {
153 4
                            $array[static::ELEMENT_NAME][$property]['_value'] = $item;
154
155 4
                            if (array_key_exists($item, $data)) {
156 4
                                foreach ($data[$item] as $attr => $val) {
157 4
                                    $array[static::ELEMENT_NAME][$property]['_attributes'][$attr] = $val;
158
                                }
159
                            }
160
                        }
161
                    } else {
162 4
                        foreach ($data as $element) {
163 4
                            if (is_array($element) && $this->isAssoc($element)) {
164 4
                                $elementData = [];
165
166 4
                                foreach ($element as $value => $attributes) {
167 4
                                    $elementData['_value'] = $value;
168
169 4
                                    foreach ($attributes as $attr => $val) {
170 4
                                        $elementData['_attributes'][$attr] = $val;
171
                                    }
172
                                }
173
174 4
                                $array[static::ELEMENT_NAME][$property][] = $elementData;
175
                            } else {
176 4
                                $array[static::ELEMENT_NAME][$property][] = $element;
177
                            }
178
                        }
179
                    }
180 18
                } elseif (null !== $data && '' !== $data) {
181 18
                    $array[static::ELEMENT_NAME][$property] = $data;
182
                }
183
            }
184
        }
185
186 20
        return $array;
187
    }
188
189
    /**
190
     * @param \ReflectionProperty $property
191
     *
192
     * @return null|array
193
     */
194 506
    private function grabData(ReflectionProperty $property): ?array
195
    {
196 506
        if (false === $property->getDocComment()) {
197
            // @codeCoverageIgnoreStart
198
            return null;
199
            // @codeCoverageIgnoreEnd
200
        }
201
202 506
        preg_match_all(
203 506
            '/
204
                        @var\s+(?\'type\'[^\s]+)|
205
                        @dataType\s+(?\'dataType\'[^\s]+)|
206
                        @attribute\s+(?\'attribute\'[^\s]+)|
207
                        @attributeDataType\s+(?\'attributeDataType\'[^\s]+)
208
                    /mx',
209 506
            $property->getDocComment(),
210 253
            $matches
211
        );
212
213
        $results = [
214 506
            'type' => null,
215
            'dataType' => null,
216
            'attributes' => [],
217
        ];
218
219 506
        foreach ($matches['type'] as $match) {
220 506
            if ('' !== $match && null !== $match) {
221 506
                $results['type'] = $match;
222
223 506
                break;
224
            }
225
        }
226
227 506
        foreach ($matches['dataType'] as $match) {
228 506
            if ('' !== $match && null !== $match) {
229 390
                $results['dataType'] = $match;
230
231 390
                break;
232
            }
233
        }
234
235 506
        foreach ($matches['attribute'] as $key => $match) {
236
            if (
237 506
                '' !== $match &&
238 506
                null !== $match &&
239 506
                isset($matches['attributeDataType'][$key + 1]) &&
240 506
                '' !== $matches['attributeDataType'][$key + 1]
241
            ) {
242 330
                $results['attributes'][$match] = $matches['attributeDataType'][$key + 1];
243
            }
244
        }
245
246 506
        return $results;
247
    }
248
}
249