Passed
Push — main ( f251de...af650d )
by Diego
03:20
created

ModelTrait::getAdder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Blackmine\Model;
6
7
use Carbon\Carbon;
8
use Carbon\CarbonImmutable;
9
use Carbon\CarbonInterface;
10
use DateTimeImmutable;
11
use Blackmine\Tool\Inflect;
12
use Doctrine\Common\Collections\ArrayCollection;
13
use Doctrine\Common\Collections\Collection;
14
use ReflectionException;
15
use ReflectionProperty;
16
17
trait ModelTrait
18
{
19
    protected static array $direct_types = [
20
        "int", "string", "float", "array", "bool", "mixed"
21
    ];
22
23
    /**
24
     * @throws ReflectionException
25
     */
26
    public function __call(string $method, array $args): mixed
27
    {
28
        $method_prefix = Inflect::extractPrefix($method);
29
30
        return match($method_prefix) {
31
            Inflect::GETTER_PREFIX => $this->getter($method),
32
            Inflect::SETTER_PREFIX => $this->setter($method, $args),
33
            Inflect::ISSER_PREFIX => $this->isser($method),
34
            Inflect::ADDER_PREFIX => $this->adder($method, $args),
35
            Inflect::REMOVER_PREFIX => $this->remover($method, $args),
36
            default => null,
37
        };
38
    }
39
40
    /**
41
     * @throws ReflectionException
42
     */
43
    protected function setter(string $method, array $args): self
44
    {
45
        $property = $this->getProperty($method);
46
        if (property_exists($this, $property) && !is_null($args[0])) {
47
            $this->$property = $this->normalizeValue($property, $this->getPropertyType($property), $args[0]);
48
        }
49
50
        return $this;
51
    }
52
53
    protected function getter(string $method): mixed
54
    {
55
        $property = $this->getProperty($method);
56
        if (property_exists($this, $property)) {
57
            return $this->$property;
58
        }
59
60
        return null;
61
    }
62
63
    protected function isser(string $method): bool
64
    {
65
        $property = $this->getProperty($method);
66
        if (property_exists($this, $property)) {
67
            return (bool) $this->$property;
68
        }
69
70
        $property = "is_" . $property;
71
        if (property_exists($this, $property)) {
72
            return (bool) $this->$property;
73
        }
74
75
        return false;
76
    }
77
78
    protected function adder(string $method, array $args): self
79
    {
80
        $property = Inflect::pluralize($this->getProperty($method));
81
        if (property_exists($this, $property) && $this->$property instanceof Collection) {
82
            $found = $this->$property->find($args[0]);
83
            if (!$found) {
84
                $this->$property->add($args[0]);
85
            }
86
        }
87
88
        return $this;
89
    }
90
91
    protected function remover(string $method, array $args): self
92
    {
93
        $property = Inflect::pluralize($this->getProperty($method));
94
        if (property_exists($this, $property) && $this->$property instanceof Collection) {
95
            $found = $this->$property->find($args[0]);
96
            if ($found) {
97
                $key = $this->$property->indexOf($found);
98
                $this->$property->remove($key);
99
            }
100
        }
101
102
        return $this;
103
    }
104
105
    protected function getSetter(string $property): string
106
    {
107
        return Inflect::SETTER_PREFIX . Inflect::camelize($property);
108
    }
109
110
    protected function getGetter(string $property): string
111
    {
112
        return Inflect::GETTER_PREFIX . Inflect::camelize($property);
113
    }
114
115
    protected function getIsser(string $property): string
116
    {
117
        return Inflect::ISSER_PREFIX . Inflect::camelize($property);
118
    }
119
120
    protected function getAdder(string $property): string
121
    {
122
        return Inflect::ADDER_PREFIX . Inflect::camelize($property);
123
    }
124
125
    protected function getRemover(string $property): string
126
    {
127
        return Inflect::REMOVER_PREFIX . Inflect::camelize($property);
128
    }
129
130
    protected function getProperty(string $method): string
131
    {
132
        $test = preg_match('/[A-Z]/', $method, $matches, PREG_OFFSET_CAPTURE);
133
        if ($test) {
134
            return Inflect::snakeize(substr($method, $matches[0][1]));
135
        }
136
137
        return $method;
138
    }
139
140
    /**
141
     * @throws ReflectionException
142
     */
143
    protected function getPropertyType(string $property): string
144
    {
145
        $p = new ReflectionProperty($this, $property);
146
        return $p->getType()?->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

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

146
        return $p->getType()?->/** @scrutinizer ignore-call */ getName();
Loading history...
147
    }
148
149
    protected function normalizeValue(string $property, string $type, mixed $raw_value): mixed
150
    {
151
        if ($this->isRelatedModel($type)) {
152
            if ($raw_value instanceof $type) {
153
                return $raw_value;
154
            }
155
156
            $value = new $type();
157
            $value->fromArray($raw_value);
158
159
            return $value;
160
        }
161
162
        if (is_array($raw_value) && $this->isCollection($type)) {
163
            return $this->populateRelation($property, $type, $raw_value);
164
        }
165
166
        if ($raw_value instanceof Collection && $this->isCollection($type)) {
167
            if (get_class($raw_value) !== $type) {
168
                if (method_exists($raw_value, "getElements")) {
169
                    return new $type($raw_value->getElements());
170
                }
171
172
                return new $type($raw_value->toArray());
173
            }
174
175
            return $raw_value;
176
        }
177
178
        if ($type === CarbonImmutable::class) {
179
            if ($raw_value instanceof CarbonImmutable) {
180
                return $raw_value;
181
            }
182
183
            if ($raw_value instanceof DateTimeImmutable) {
184
                return CarbonImmutable::createFromTimestamp($raw_value->getTimestamp());
185
            }
186
187
            $timestamp = strtotime($raw_value);
188
            return Carbon::createFromTimestamp($timestamp)->toImmutable();
189
        }
190
191
        if ($this->isDirectType($type)) {
192
            return $raw_value;
193
        }
194
195
        return null;
196
    }
197
198
    protected function isDirectType(string $type): bool
199
    {
200
        return in_array($type, self::$direct_types, true);
201
    }
202
203
    protected function populateRelation(string $property, string $type, array $raw_value): ?ArrayCollection
204
    {
205
        $repository_class = static::getRepositoryClass();
206
        $model_class = $repository_class::getRelationClassFor($property);
207
208
        if ($model_class) {
209
            $ret = new $type();
210
            foreach ($raw_value as $item) {
211
                if ($item instanceof $model_class) {
212
                    $ret->add($item);
213
                }
214
215
                if (is_array($item)) {
216
                    $model = new $model_class();
217
                    $model->fromArray($item);
218
                    $ret->add($model);
219
                }
220
            }
221
222
            return $ret;
223
        }
224
225
        return new $type($raw_value);
226
    }
227
228
    protected function isRelatedModel(string $type): bool
229
    {
230
        if (class_exists($type)) {
231
            $interfaces = class_implements($type);
232
            return $interfaces && in_array(ModelInterface::class, $interfaces, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression $interfaces of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
233
        }
234
235
        return false;
236
    }
237
238
    protected function isCollection(string $type): bool
239
    {
240
        if (class_exists($type)) {
241
            $interfaces = class_implements($type);
242
            return $interfaces && in_array(Collection::class, $interfaces, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression $interfaces of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
243
        }
244
245
        return false;
246
    }
247
}
248