Completed
Push — master ( 44b3a0...f3952b )
by Joe
02:05
created

ComHasAttributes::objectToArray()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
dl 0
loc 16
ccs 0
cts 9
cp 0
rs 10
c 1
b 0
f 0
cc 4
nc 4
nop 1
crap 20
1
<?php
2
3
namespace PhpWinTools\WmiScripting\Concerns;
4
5
use Exception;
6
use Illuminate\Support\Str;
0 ignored issues
show
Bug introduced by
The type Illuminate\Support\Str was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use ReflectionClass;
8
use PhpWinTools\WmiScripting\Contracts\Arrayable;
9
use Illuminate\Contracts\Support\Arrayable as IlluminateArrayable;
10
11
trait ComHasAttributes
12
{
13
    use CanCollect;
14
15
    protected $unmapped_attributes = [];
16
17
    protected $attribute_name_replacements = [];
18
19
    protected $trait_name_replacements = [];
20
21
    protected $trait_hidden_attributes = [
22
        'trait_hidden_attributes',
23
        'trait_name_replacements',
24
25
        'attribute_name_replacements',
26
        'unmapped_attributes',
27
28
        'hidden_attributes',
29
        'merge_parent_hidden_attributes',
30
31
        'attribute_casting',
32
        'merge_parent_casting',
33
    ];
34
35 8
    public function getAttribute($attribute, $default = null)
36
    {
37 8
        if ($this->hasAttributeMethod($attribute)) {
38 5
            $method = 'get' . ucfirst($this->snackCaseToCamel($attribute)) . 'Attribute';
39 5
            if ($this->hasProperty($attribute)) {
40 4
                return $this->{$method}($this->{$attribute});
41 2
            } elseif ($this->hasUnmappedAttribute($attribute)) {
42 1
                return  $this->{$method}($this->unmapped_attributes[$attribute]);
43
            }
44
45 1
            return $this->{$method}();
46
        }
47
48 5
        if ($this->hasProperty($attribute)) {
49 3
            return $this->{$attribute};
50
        }
51
52 2
        if ($key = array_search($attribute, $this->attribute_name_replacements)) {
53
            return $this->{$key};
54
        }
55
56 2
        if (array_key_exists($attribute, $this->unmapped_attributes)) {
57 1
            return $this->unmapped_attributes[$attribute];
58
        }
59
60 1
        return $default;
61
    }
62
63 5
    public function snackCaseToCamel(string $string)
64
    {
65 5
        $string = ucwords(str_replace(['-', '_'], ' ', $string));
66
67 5
        return lcfirst(str_replace(' ', '', $string));
68
    }
69
70 8
    public function camelCaseToSnack(string $string)
71
    {
72 8
        $string = preg_replace('/\s+/u', '', ucwords($string));
73
74 8
        return strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1_', $string));
75
    }
76
77
    public function toArray(): array
78
    {
79
        return array_merge(
80
            $this->getCalculatedAttributes(),
81
            $this->iterateAttributes(get_class_vars(get_called_class())),
82
            $this->iterateAttributes($this->unmapped_attributes)
83
        );
84
    }
85
86 2
    public function setHidden(array $hidden_attributes, bool $merge_hidden = true)
87
    {
88 2
        $hidden_attributes = $merge_hidden
89 2
            ? array_merge($this->getAncestorProperty('hidden_attributes'), $hidden_attributes)
90 2
            : $hidden_attributes;
91
92 2
        $this->trait_hidden_attributes = array_merge($this->trait_hidden_attributes, $hidden_attributes);
93
94 2
        return $this;
95
    }
96
97
    public function isHidden($key): bool
98
    {
99
        $this->trait_hidden_attributes = array_combine($this->trait_hidden_attributes, $this->trait_hidden_attributes);
100
101
        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

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