Passed
Push — master ( 78cea0...e6e26f )
by Joe
02:51
created

HasArrayableAttributes::getHiddenAttributes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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