Completed
Push — master ( 129456...b25aba )
by Joe
01:45
created

ComHasAttributes::camelCaseToSnack()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
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 PhpWinTools\Support\StringModule;
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' . StringModule::studly($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
    public function toArray(): array
64
    {
65
        return array_merge(
66
            $this->getCalculatedAttributes(),
67
            $this->iterateAttributes(get_class_vars(get_called_class())),
68
            $this->iterateAttributes($this->unmapped_attributes)
69
        );
70
    }
71
72 2
    public function setHidden(array $hidden_attributes, bool $merge_hidden = true)
73
    {
74 2
        $hidden_attributes = $merge_hidden
75 2
            ? array_merge($this->getAncestorProperty('hidden_attributes'), $hidden_attributes)
76 2
            : $hidden_attributes;
77
78 2
        $this->trait_hidden_attributes = array_merge($this->trait_hidden_attributes, $hidden_attributes);
79
80 2
        return $this;
81
    }
82
83
    public function isHidden($key): bool
84
    {
85
        $this->trait_hidden_attributes = array_combine($this->trait_hidden_attributes, $this->trait_hidden_attributes);
86
87
        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

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