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