Completed
Push — traitrefactor ( b082e9...ba1500 )
by Joe
04:02
created

HasArrayableAttributes::objectToArray()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 16
rs 10
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
namespace PhpWinTools\WmiScripting\Concerns;
4
5
use Exception;
6
use PhpWinTools\Support\StringModule;
7
use PhpWinTools\Support\BooleanModule;
8
use PhpWinTools\WmiScripting\Contracts\Arrayable;
9
use PhpWinTools\WmiScripting\Collections\ArrayCollection;
10
use function PhpWinTools\WmiScripting\Support\class_has_property;
11
use function PhpWinTools\WmiScripting\Support\class_has_trait;
12
use Illuminate\Contracts\Support\Arrayable as IlluminateArrayable;
13
use function PhpWinTools\WmiScripting\Support\get_ancestor_property;
14
15
trait HasArrayableAttributes
16
{
17
    protected $unmapped_attributes = [];
18
19
    protected $attribute_name_replacements = [];
20
21
    public function getAttribute($attribute, $default = null)
22
    {
23
        if ($this->hasAttributeMethod($attribute)) {
24
            return $this->getAttributeMethodValue('get' . StringModule::studly($attribute) . 'Attribute', $attribute);
25
        }
26
27
        $value = $default;
28
29
        if (class_has_property(get_called_class(), $attribute)) {
30
            $value = $this->{$attribute};
31
        }
32
33
        if ($key = array_search($attribute, $this->attribute_name_replacements)) {
34
            $value = $this->{$key};
35
        }
36
37
        if (array_key_exists($attribute, $this->unmapped_attributes)) {
38
            $value = $this->unmapped_attributes[$attribute];
39
        }
40
41
        if (class_has_trait(get_called_class(), HasCastableAttributes::class) && $this->hasCast($attribute)) {
1 ignored issue
show
Bug introduced by
It seems like hasCast() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

41
        if (class_has_trait(get_called_class(), HasCastableAttributes::class) && $this->/** @scrutinizer ignore-call */ hasCast($attribute)) {
Loading history...
42
            $value = $this->cast($attribute, $value);
1 ignored issue
show
Bug introduced by
It seems like cast() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

42
            /** @scrutinizer ignore-call */ 
43
            $value = $this->cast($attribute, $value);
Loading history...
43
        }
44
45
        return $value;
46
    }
47
48
    public function toArray(): array
49
    {
50
        return array_merge(
51
            $this->getCalculatedAttributes(),
52
            $this->iterateAttributes(get_class_vars(get_called_class())),
53
            $this->iterateAttributes($this->unmapped_attributes)
54
        );
55
    }
56
57
    public function collect(array $array)
58
    {
59
        return ArrayCollection::collect($array);
60
    }
61
62
    public function setUnmappedAttribute($key, $value)
63
    {
64
        $this->unmapped_attributes[$key] = $value;
65
66
        return $this;
67
    }
68
69
    protected function getAttributeMethodValue($method, $attribute)
70
    {
71
        if (class_has_property(get_called_class(), $attribute)) {
72
            return $this->{$method}($this->{$attribute});
73
        }
74
75
        if ($this->hasUnmappedAttribute($attribute)) {
76
            return  $this->{$method}($this->unmapped_attributes[$attribute]);
77
        }
78
79
        return $this->{$method}($attribute);
80
    }
81
82
    protected function getCalculatedAttributes()
83
    {
84
        return $this->collect($this->calculatedAttributes())->mapWithKeys(function ($attribute) {
85
            return [$attribute => $this->{'get' . lcfirst($attribute) . 'Attribute'}()];
86
        })->toArray();
87
    }
88
89
    protected function calculatedAttributes()
90
    {
91
        return $this->collect($this->getAttributeMethods())->map(function ($method) {
92
            return $this->getAttributeNameFromMethod($method);
93
        })->filter(function ($attribute) {
94
            return !class_has_property(get_called_class(), $attribute) && !$this->hasUnmappedAttribute($attribute);
95
        })->values()->toArray();
96
    }
97
98
    protected function hasUnmappedAttribute($attribute)
99
    {
100
        return array_key_exists($attribute, $this->unmapped_attributes);
101
    }
102
103
    protected function hasAttributeMethod($attribute)
104
    {
105
        return $this->collect($this->getAttributeMethods())->filter(function ($method) use ($attribute) {
106
            if (($method_name = $this->getAttributeNameFromMethod($method)) === $attribute) {
107
                return true;
108
            }
109
110
            $method_name = StringModule::snake($method_name);
111
112
            if ($method_name === $attribute) {
113
                return true;
114
            }
115
116
            return false;
117
        })->isNotEmpty();
118
    }
119
120
    protected function getAttributeNameFromMethod($method)
121
    {
122
        return lcfirst(substr($method, 3, -9));
123
    }
124
125
    protected function getAttributeMethods()
126
    {
127
        return $this->collect(get_class_methods(get_called_class()))->filter(function ($method) {
128
            return substr($method, 0, 3) === 'get' && substr($method, -9) === 'Attribute' && $method !== 'getAttribute';
129
        })->values()->toArray();
130
    }
131
132
    protected function replaceAttributeName($key)
133
    {
134
        if (array_key_exists($key, $this->attribute_name_replacements)) {
135
            return $this->attribute_name_replacements[$key];
136
        }
137
138
        return $key;
139
    }
140
141
    protected function objectToArray($value)
142
    {
143
        if (!is_object($value)) {
144
            return $value;
145
        }
146
147
        $array = (array) $value;
148
149
        foreach ($array as $key => $value) {
0 ignored issues
show
introduced by
$value is overwriting one of the parameters of this function.
Loading history...
150
            if (strpos($key, "\x00*\x00") === 0) {
151
                $array[substr($key, 3)] = $value;
152
                unset($array[$key]);
153
            }
154
        }
155
156
        return $array;
157
    }
158
159
    protected function tryToArrayMethod($value)
160
    {
161
        if (!is_object($value)) {
162
            return $value;
163
        }
164
165
        if (is_object($value) && $this->hasToArrayMethod($value)) {
166
            try {
167
                $array = $value->toArray();
168
                if (is_array($array)) {
169
                    return $array;
170
                }
171
            } catch (Exception $exception) {
172
                return $value;
173
            }
174
        }
175
176
        return $value;
177
    }
178
179
    protected function hasToArrayMethod($value)
180
    {
181
        if (!is_object($value)) {
182
            return false;
183
        }
184
185
        if (in_array('toArray', get_class_methods($value))) {
186
            return true;
187
        }
188
189
        return false;
190
    }
191
192
    protected function transformToArray($value)
193
    {
194
        if (is_object($value) && ($value instanceof Arrayable || $value instanceof IlluminateArrayable)) {
195
            return $value->toArray();
196
        }
197
198
        if (is_object($value) && is_array(($array = $this->tryToArrayMethod($value)))) {
199
            return $array;
200
        }
201
202
        if (is_object($value)) {
203
            return $this->objectToArray($value);
204
        }
205
206
        if (is_array($value)) {
207
            return $this->iterateAttributes($value);
208
        }
209
210
        return $value;
211
    }
212
213
    protected function iterateAttributes(array $attributes)
214
    {
215
        $results = [];
216
217
        foreach ($attributes as $key => $value) {
218
            if (class_has_trait(get_called_class(), HasHiddenAttributes::class) && $this->isHidden($key)) {
0 ignored issues
show
Bug introduced by
It seems like isHidden() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

218
            if (class_has_trait(get_called_class(), HasHiddenAttributes::class) && $this->/** @scrutinizer ignore-call */ isHidden($key)) {
Loading history...
219
                continue;
220
            }
221
222
            $results[$this->replaceAttributeName($key)] = $this->transformToArray($this->getAttribute($key, $value));
223
        }
224
225
        return $results;
226
    }
227
}
228