Completed
Push — master ( ce8031...13fea1 )
by Joe
02:38
created

ComHasAttributes   F

Complexity

Total Complexity 68

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Test Coverage

Coverage 40.3%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 68
eloc 129
c 1
b 0
f 0
dl 0
loc 290
ccs 54
cts 134
cp 0.403
rs 2.96

25 Methods

Rating   Name   Duplication   Size   Complexity  
A setHidden() 0 9 2
A replaceAttributeName() 0 7 2
B getAttribute() 0 26 7
A isHidden() 0 5 1
A getAttributeMethods() 0 5 3
A calculatedAttributes() 0 7 2
A getCalculatedAttributes() 0 5 1
A toArray() 0 6 1
A getAttributeNameFromMethod() 0 3 1
A getCasts() 0 3 1
B transformToArray() 0 19 8
A tryToArrayMethod() 0 18 6
A hasUnmappedAttribute() 0 3 1
A iterateAttributes() 0 13 3
A hasProperty() 0 3 1
A hasToArrayMethod() 0 11 3
A setAttributeNameReplacements() 0 5 1
A getHidden() 0 3 1
A hasAttributeMethod() 0 5 1
A objectToArray() 0 16 4
A hasCast() 0 3 1
C cast() 0 28 13
A getAncestorProperty() 0 5 1
A setCasts() 0 5 2
A getCast() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like ComHasAttributes 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 ComHasAttributes, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpWinTools\WmiScripting\Concerns;
4
5
use Exception;
6
use ReflectionClass;
7
use PhpWinTools\WmiScripting\Contracts\Arrayable;
8
use Illuminate\Contracts\Support\Arrayable as IlluminateArrayable;
9
10
trait ComHasAttributes
11
{
12
    use CanCollect;
13
14
    protected $unmapped_attributes = [];
15
16
    protected $trait_name_replacements = [];
17
18
    protected $trait_hidden_attributes = [
19
        'trait_hidden_attributes',
20
        'trait_name_replacements',
21
22
        'attribute_name_replacements',
23
        'unmapped_attributes',
24
25
        'hidden_attributes',
26
        'merge_parent_hidden_attributes',
27
28
        'attribute_casting',
29
        'merge_parent_casting',
30
    ];
31
32 2
    public function getAttribute($attribute, $default = null)
33
    {
34 2
        if ($this->hasAttributeMethod($attribute)) {
35
            $method = 'get' . ucfirst($attribute) . 'Attribute';
36
            if ($this->hasProperty($attribute)) {
37
                return $this->{$method}($this->{$attribute});
38
            } elseif ($this->hasUnmappedAttribute($attribute)) {
39
                return  $this->{$method}($this->unmapped_attributes[$attribute]);
40
            }
41
42
            return $this->{$method}();
43
        }
44
45 2
        if ($this->hasProperty($attribute)) {
46 2
            return $this->{$attribute};
47
        }
48
49
        if ($key = array_search($attribute, $this->attribute_name_replacements)) {
50
            return $this->{$key};
51
        }
52
53
        if (array_key_exists($attribute, $this->unmapped_attributes)) {
54
            return $this->unmapped_attributes[$attribute];
55
        }
56
57
        return $default;
58
    }
59
60
    public function toArray(): array
61
    {
62
        return array_merge(
63
            $this->getCalculatedAttributes(),
64
            $this->iterateAttributes(get_class_vars(get_called_class())),
65
            $this->iterateAttributes($this->unmapped_attributes)
66
        );
67
    }
68
69 2
    public function setHidden(array $hidden_attributes, bool $merge_hidden = true)
70
    {
71 2
        $hidden_attributes = $merge_hidden
72 2
            ? array_merge($this->getAncestorProperty('hidden_attributes'), $hidden_attributes)
73 2
            : $hidden_attributes;
74
75 2
        $this->trait_hidden_attributes = array_merge($this->trait_hidden_attributes, $hidden_attributes);
76
77 2
        return $this;
78
    }
79
80
    public function isHidden($key): bool
81
    {
82
        $this->trait_hidden_attributes = array_combine($this->trait_hidden_attributes, $this->trait_hidden_attributes);
83
84
        return array_key_exists($key, $this->trait_hidden_attributes);
0 ignored issues
show
Bug introduced by
It seems like $this->trait_hidden_attributes can also be of type false; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

84
        return array_key_exists($key, /** @scrutinizer ignore-type */ $this->trait_hidden_attributes);
Loading history...
85
    }
86
87 2
    public function getHidden()
88
    {
89 2
        return array_combine($this->trait_hidden_attributes, $this->trait_hidden_attributes);
90
    }
91
92
    protected function getCalculatedAttributes()
93
    {
94
        return $this->collect($this->calculatedAttributes())->mapWithKeys(function ($attribute) {
95
            return [$attribute => $this->{'get' . lcfirst($attribute) . 'Attribute'}()];
96
        })->toArray();
97
    }
98
99
    protected function calculatedAttributes()
100
    {
101
        return $this->collect($this->getAttributeMethods())->map(function ($method) {
102
            return $this->getAttributeNameFromMethod($method);
103
        })->filter(function ($attribute) {
104
            return !$this->hasProperty($attribute) && !$this->hasUnmappedAttribute($attribute);
105
        })->values()->toArray();
106
    }
107
108
    protected function hasUnmappedAttribute($attribute)
109
    {
110
        return array_key_exists($attribute, $this->unmapped_attributes);
111
    }
112
113 2
    protected function hasAttributeMethod($attribute)
114
    {
115
        return $this->collect($this->getAttributeMethods())->filter(function ($method) use ($attribute) {
116 2
            return $this->getAttributeNameFromMethod($method) === $attribute;
117 2
        })->isNotEmpty();
118
    }
119
120 2
    protected function getAttributeNameFromMethod($method)
121
    {
122 2
        return lcfirst(substr($method, 3, -9));
123
    }
124
125 2
    protected function getAttributeMethods()
126
    {
127
        return $this->collect(get_class_methods(get_called_class()))->filter(function ($method) {
128 2
            return substr($method, 0, 3) === 'get' && substr($method, -9) === 'Attribute' && $method !== 'getAttribute';
129 2
        })->values()->toArray();
130
    }
131
132 2
    protected function setAttributeNameReplacements(array $name_replacements)
133
    {
134 2
        $this->trait_name_replacements = $name_replacements;
135
136 2
        return $this;
137
    }
138
139 2
    protected function hasProperty($property_name)
140
    {
141 2
        return array_key_exists($property_name, get_class_vars(get_called_class()));
142
    }
143
144
    protected function replaceAttributeName($key)
145
    {
146
        if (array_key_exists($key, $this->trait_name_replacements)) {
147
            return $this->trait_name_replacements[$key];
148
        }
149
150
        return $key;
151
    }
152
153
    protected function objectToArray($value)
154
    {
155
        if (!is_object($value)) {
156
            return $value;
157
        }
158
159
        $array = (array) $value;
160
161
        foreach ($array as $key => $value) {
0 ignored issues
show
introduced by
$value is overwriting one of the parameters of this function.
Loading history...
162
            if (strpos($key, "\x00*\x00") === 0) {
163
                $array[substr($key, 3)] = $value;
164
                unset($array[$key]);
165
            }
166
        }
167
168
        return $array;
169
    }
170
171
    protected function tryToArrayMethod($value)
172
    {
173
        if (!is_object($value)) {
174
            return $value;
175
        }
176
177
        if (is_object($value) && $this->hasToArrayMethod($value)) {
178
            try {
179
                $array = $value->toArray();
180
                if (is_array($array)) {
181
                    return $array;
182
                }
183
            } catch (Exception $exception) {
184
                return $value;
185
            }
186
        }
187
188
        return $value;
189
    }
190
191
    protected function hasToArrayMethod($value)
192
    {
193
        if (!is_object($value)) {
194
            return false;
195
        }
196
197
        if (in_array('toArray', get_class_methods($value))) {
198
            return true;
199
        }
200
201
        return false;
202
    }
203
204
    protected function transformToArray($value)
205
    {
206
        if (is_object($value) && ($value instanceof Arrayable || $value instanceof IlluminateArrayable)) {
207
            return $value->toArray();
208
        }
209
210
        if (is_object($value) && is_array(($array = $this->tryToArrayMethod($value)))) {
211
            return $array;
212
        }
213
214
        if (is_object($value)) {
215
            return $this->objectToArray($value);
216
        }
217
218
        if (is_array($value)) {
219
            return $this->iterateAttributes($value);
220
        }
221
222
        return $value;
223
    }
224
225
    protected function iterateAttributes(array $attributes)
226
    {
227
        $results = [];
228
229
        foreach ($attributes as $key => $value) {
230
            if ($this->isHidden($key)) {
231
                continue;
232
            }
233
234
            $results[$this->replaceAttributeName($key)] = $this->transformToArray($this->getAttribute($key, $value));
235
        }
236
237
        return $results;
238
    }
239
240 2
    public function getCasts(): array
241
    {
242 2
        return $this->attribute_casting;
243
    }
244
245 2
    public function getCast($attribute)
246
    {
247 2
        $casts = $this->getCasts();
248
249 2
        return $casts[$attribute] ?? null;
250
    }
251
252 2
    public function hasCast($attribute): bool
253
    {
254 2
        return array_key_exists($attribute, $this->attribute_casting);
255
    }
256
257 2
    public function setCasts(array $attribute_casting, bool $merge_casting = true)
258
    {
259 2
        $this->attribute_casting = $merge_casting
0 ignored issues
show
Bug Best Practice introduced by
The property attribute_casting does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
260 2
            ? array_merge($this->getAncestorProperty('attribute_casting'), $attribute_casting)
261
            : $attribute_casting;
262 2
    }
263
264 2
    protected function cast($key, $value)
265
    {
266 2
        $casts = $this->getCasts();
267
268 2
        if (!$this->hasCast($key)) {
269 2
            return $value;
270
        }
271
272 2
        if (is_callable($casts[$key])) {
273
            return $casts[$key]($value, $key);
274
        }
275
276 2
        switch ($casts[$key]) {
277 2
            case 'array':
278
                return is_array($value) ? $value : [$value];
279 2
            case 'bool':
280 2
            case 'boolean':
281
                return (bool) $value;
282 2
            case 'float':
283
                return (float) $value;
284 2
            case 'int':
285 2
            case 'integer':
286
                // Prevent integer overflow
287 2
                return $value >= PHP_INT_MAX || $value <= PHP_INT_MIN ? (string) $value : (int) $value;
288 2
            case 'string':
289 2
                return (string) $value;
290
            default:
291
                return $value;
292
        }
293
    }
294
295 2
    protected function getAncestorProperty($property_name)
296
    {
297
        return $this->collect(class_parents($this))->map(function ($class) use ($property_name) {
298 2
            return (new ReflectionClass($class))->getDefaultProperties()[$property_name] ?? [];
299 2
        })->values()->collapse()->toArray();
300
    }
301
}
302