Completed
Push — master ( 1c61d9...2c0a26 )
by Joe
02:01
created

ComHasAttributes::collect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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