Completed
Push — master ( 1ebb0e...4253c0 )
by Joe
02:03
created

ComHasAttributes::getHiddenAttributes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 10
cc 2
nc 2
nop 0
crap 2.0625
1
<?php
2
3
namespace PhpWinTools\WmiScripting\Concerns;
4
5
use Exception;
6
use PhpWinTools\Support\BooleanModule;
7
use ReflectionClass;
8
use PhpWinTools\Support\StringModule;
9
use PhpWinTools\WmiScripting\Contracts\Arrayable;
10
use PhpWinTools\WmiScripting\Collections\ArrayCollection;
11
use Illuminate\Contracts\Support\Arrayable as IlluminateArrayable;
12
13
trait ComHasAttributes
14
{
15
    protected $hidden_booted = false;
16
17
    protected $casts_booted = false;
18
19
    protected $unmapped_attributes = [];
20
21
    protected $attribute_name_replacements = [];
22
23
    protected $trait_hidden_attributes = [
24
        'trait_hidden_attributes',
25
        'trait_name_replacements',
26
27
        'attribute_name_replacements',
28
        'unmapped_attributes',
29
30
        'hidden_attributes',
31
        'merge_parent_hidden_attributes',
32
33
        'attribute_casting',
34
        'merge_parent_casting',
35
    ];
36
37 16
    public function getAttribute($attribute, $default = null)
38
    {
39 16
        if ($this->hasAttributeMethod($attribute)) {
40 5
            return $this->getAttributeMethodValue('get' . StringModule::studly($attribute) . 'Attribute', $attribute);
41
        }
42
43 13
        $value = $default;
44
45 13
        if ($this->hasProperty($attribute)) {
46 9
            $value = $this->{$attribute};
47
        }
48
49 13
        if ($key = array_search($attribute, $this->attribute_name_replacements)) {
50 1
            $value = $this->{$key};
51
        }
52
53 13
        if (array_key_exists($attribute, $this->unmapped_attributes)) {
54 1
            $value = $this->unmapped_attributes[$attribute];
55
        }
56
57 13
        if ($this->hasCast($attribute)) {
58 6
            $value = $this->cast($attribute, $value);
59
        }
60
61 13
        return $value;
62
    }
63
64 3
    public function toArray(): array
65
    {
66 3
        return array_merge(
67 3
            $this->getCalculatedAttributes(),
68 3
            $this->iterateAttributes(get_class_vars(get_called_class())),
69 3
            $this->iterateAttributes($this->unmapped_attributes)
70
        );
71
    }
72
73 17
    public function collect(array $array)
74
    {
75 17
        return new ArrayCollection($array);
76
    }
77
78 17
    public function mergeHiddenAttributes(array $hidden_attributes, bool $merge_hidden = true)
79
    {
80 17
        $hidden_attributes = $merge_hidden
81 17
            ? array_merge($this->getAncestorProperty('hidden_attributes'), $hidden_attributes)
82 17
            : $hidden_attributes;
83
84 17
        $this->trait_hidden_attributes = array_merge($this->trait_hidden_attributes, $hidden_attributes);
85
86 17
        $this->bootHiddenAttributes();
87
88 17
        return $this;
89
    }
90
91 5
    public function getHiddenAttributes()
92
    {
93 5
        if (!$this->hidden_booted) {
94
            $this->bootHiddenAttributes();
95
        }
96
97 5
        return $this->trait_hidden_attributes;
98
    }
99
100 3
    public function isHidden($key): bool
101
    {
102 3
        return array_key_exists($key, $this->getHiddenAttributes());
103
    }
104
105 2
    public function setUnmappedAttribute($key, $value)
106
    {
107 2
        $this->unmapped_attributes[$key] = $value;
108
109 2
        return $this;
110
    }
111
112 13
    public function getCasts(): array
113
    {
114 13
        if (!$this->casts_booted) {
115 13
            $this->bootCasts();
116
        }
117
118 13
        return $this->attribute_casting;
119
    }
120
121 2
    public function getCast($attribute)
122
    {
123 2
        $casts = $this->getCasts();
124
125 2
        return $casts[$attribute] ?? null;
126
    }
127
128 13
    public function hasCast($attribute): bool
129
    {
130 13
        return array_key_exists($attribute, $this->getCasts());
131
    }
132
133 13
    protected function bootCasts()
134
    {
135 13
        $merge_casting = true;
136 13
        $attribute_casting = [];
137
138 13
        if ($this->hasProperty('merge_parent_casting')) {
139 13
            $merge_casting = $this->merge_parent_casting;
140
        }
141
142 13
        if ($this->hasProperty('attribute_casting')) {
143 13
            $attribute_casting = $this->attribute_casting;
144
        }
145
146 13
        $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...
147 13
            ? array_merge($this->getAncestorProperty('attribute_casting'), $attribute_casting)
148
            : $attribute_casting;
149
150 13
        $this->casts_booted = true;
151 13
    }
152
153 17
    protected function bootHiddenAttributes()
154
    {
155 17
        $this->trait_hidden_attributes = array_combine($this->trait_hidden_attributes, $this->trait_hidden_attributes);
156
157 17
        $this->hidden_booted = true;
158 17
    }
159
160
161 5
    protected function getAttributeMethodValue($method, $attribute)
162
    {
163 5
        if ($this->hasProperty($attribute)) {
164 3
            return $this->{$method}($this->{$attribute});
165
        }
166
167 2
        if ($this->hasUnmappedAttribute($attribute)) {
168 1
            return  $this->{$method}($this->unmapped_attributes[$attribute]);
169
        }
170
171 1
        return $this->{$method}($attribute);
172
    }
173
174 3
    protected function getCalculatedAttributes()
175
    {
176
        return $this->collect($this->calculatedAttributes())->mapWithKeys(function ($attribute) {
177 3
            return [$attribute => $this->{'get' . lcfirst($attribute) . 'Attribute'}()];
178 3
        })->toArray();
179
    }
180
181 3
    protected function calculatedAttributes()
182
    {
183
        return $this->collect($this->getAttributeMethods())->map(function ($method) {
184 3
            return $this->getAttributeNameFromMethod($method);
185
        })->filter(function ($attribute) {
186 3
            return !$this->hasProperty($attribute) && !$this->hasUnmappedAttribute($attribute);
187 3
        })->values()->toArray();
188
    }
189
190 5
    protected function hasUnmappedAttribute($attribute)
191
    {
192 5
        return array_key_exists($attribute, $this->unmapped_attributes);
193
    }
194
195 16
    protected function hasAttributeMethod($attribute)
196
    {
197
        return $this->collect($this->getAttributeMethods())->filter(function ($method) use ($attribute) {
198 16
            if (($method_name = $this->getAttributeNameFromMethod($method)) === $attribute) {
199 3
                return true;
200
            }
201
202 16
            $method_name = StringModule::snake($method_name);
203
204 16
            if ($method_name === $attribute) {
205 3
                return true;
206
            }
207
208 16
            return false;
209 16
        })->isNotEmpty();
210
    }
211
212 16
    protected function getAttributeNameFromMethod($method)
213
    {
214 16
        return lcfirst(substr($method, 3, -9));
215
    }
216
217 16
    protected function getAttributeMethods()
218
    {
219
        return $this->collect(get_class_methods(get_called_class()))->filter(function ($method) {
220 16
            return substr($method, 0, 3) === 'get' && substr($method, -9) === 'Attribute' && $method !== 'getAttribute';
221 16
        })->values()->toArray();
222
    }
223
224 16
    public function hasProperty($property_name)
225
    {
226 16
        return array_key_exists($property_name, get_class_vars(get_called_class()));
227
    }
228
229 3
    protected function replaceAttributeName($key)
230
    {
231 3
        if (array_key_exists($key, $this->attribute_name_replacements)) {
232 1
            return $this->attribute_name_replacements[$key];
233
        }
234
235 3
        return $key;
236
    }
237
238 6
    protected function cast($key, $value)
239
    {
240 6
        $casts = $this->getCasts();
241
242 6
        if (!$this->hasCast($key)) {
243 2
            return $value;
244
        }
245
246
        /* @TODO: This isn't needed. Need to write tests around it so I can remove it. */
247 6
        if (is_callable($casts[$key])) {
248
            return $casts[$key]($value, $key);
249
        }
250
251 6
        switch ($casts[$key]) {
252 6
            case 'array':
253 1
                return is_array($value) ? $value : [$value];
254 5
            case 'bool':
255 4
            case 'boolean':
256 1
                return BooleanModule::makeBoolean($value);
257 4
            case 'float':
258
                return (float) $value;
259 4
            case 'int':
260 3
            case 'integer':
261
                // Prevent integer overflow
262 4
                return $value >= PHP_INT_MAX || $value <= PHP_INT_MIN ? (string) $value : (int) $value;
263 2
            case 'string':
264 2
                return (string) $value;
265
            default:
266
                return $value;
267
        }
268
    }
269
270
    protected function objectToArray($value)
271
    {
272
        if (!is_object($value)) {
273
            return $value;
274
        }
275
276
        $array = (array) $value;
277
278
        foreach ($array as $key => $value) {
0 ignored issues
show
introduced by
$value is overwriting one of the parameters of this function.
Loading history...
279
            if (strpos($key, "\x00*\x00") === 0) {
280
                $array[substr($key, 3)] = $value;
281
                unset($array[$key]);
282
            }
283
        }
284
285
        return $array;
286
    }
287
288
    protected function tryToArrayMethod($value)
289
    {
290
        if (!is_object($value)) {
291
            return $value;
292
        }
293
294
        if (is_object($value) && $this->hasToArrayMethod($value)) {
295
            try {
296
                $array = $value->toArray();
297
                if (is_array($array)) {
298
                    return $array;
299
                }
300
            } catch (Exception $exception) {
301
                return $value;
302
            }
303
        }
304
305
        return $value;
306
    }
307
308
    protected function hasToArrayMethod($value)
309
    {
310
        if (!is_object($value)) {
311
            return false;
312
        }
313
314
        if (in_array('toArray', get_class_methods($value))) {
315
            return true;
316
        }
317
318
        return false;
319
    }
320
321 3
    protected function transformToArray($value)
322
    {
323 3
        if (is_object($value) && ($value instanceof Arrayable || $value instanceof IlluminateArrayable)) {
324
            return $value->toArray();
325
        }
326
327 3
        if (is_object($value) && is_array(($array = $this->tryToArrayMethod($value)))) {
328
            return $array;
329
        }
330
331 3
        if (is_object($value)) {
332
            return $this->objectToArray($value);
333
        }
334
335 3
        if (is_array($value)) {
336
            return $this->iterateAttributes($value);
337
        }
338
339 3
        return $value;
340
    }
341
342 3
    protected function iterateAttributes(array $attributes)
343
    {
344 3
        $results = [];
345
346 3
        foreach ($attributes as $key => $value) {
347 3
            if ($this->isHidden($key)) {
348 3
                continue;
349
            }
350
351 3
            $results[$this->replaceAttributeName($key)] = $this->transformToArray($this->getAttribute($key, $value));
352
        }
353
354 3
        return $results;
355
    }
356
357 17
    protected function getAncestorProperty($property_name)
358
    {
359
        return $this->collect(class_parents($this))->map(function ($class) use ($property_name) {
360 16
            return (new ReflectionClass($class))->getDefaultProperties()[$property_name] ?? [];
361 17
        })->values()->collapse()->toArray();
362
    }
363
}
364