1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /** |
||
6 | * Handles the hydration of objects/models for results. |
||
7 | */ |
||
8 | |||
9 | namespace Pixie\Hydration; |
||
10 | |||
11 | use Exception; |
||
12 | use stdClass; |
||
13 | use Throwable; |
||
14 | use function is_object; |
||
15 | use function method_exists; |
||
16 | use function trim; |
||
17 | use function ucfirst; |
||
18 | |||
19 | /** |
||
20 | * @template T |
||
21 | */ |
||
22 | class Hydrator |
||
23 | { |
||
24 | /** |
||
25 | * The model to hydrate |
||
26 | |||
27 | * |
||
28 | * @var class-string<T> |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
29 | */ |
||
30 | protected $model; |
||
31 | |||
32 | /** |
||
33 | * The arguments used to create the new instance. |
||
34 | * |
||
35 | * @var array<string|int, mixed> |
||
36 | */ |
||
37 | protected $constructorArgs; |
||
38 | |||
39 | /** |
||
40 | |||
41 | * @param class-string<T> $model |
||
0 ignored issues
–
show
|
|||
42 | * @param array<string|int, mixed> $constructorArgs |
||
43 | */ |
||
44 | public function __construct(string $model = stdClass::class, array $constructorArgs = []) |
||
45 | { |
||
46 | $this->model = $model; |
||
47 | $this->constructorArgs = $constructorArgs; |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * Map many models |
||
52 | * |
||
53 | * @param array<int, object|mixed[]> $sources |
||
54 | * |
||
55 | * @return array<T> |
||
56 | */ |
||
57 | public function fromMany(array $sources): array |
||
58 | { |
||
59 | return array_map([$this, 'from'], $sources); |
||
60 | } |
||
61 | |||
62 | /** |
||
63 | * Map a single model |
||
64 | * |
||
65 | * @param object|mixed[] $source |
||
66 | * |
||
67 | * @return T |
||
0 ignored issues
–
show
The type
Pixie\Hydration\T 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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||
68 | */ |
||
69 | public function from($source) |
||
70 | { |
||
71 | switch (true) { |
||
72 | case is_array($source): |
||
73 | return $this->fromArray($source); |
||
74 | |||
75 | case is_object($source): |
||
76 | return $this->fromObject($source); |
||
77 | |||
78 | default: |
||
79 | throw new Exception('Models can only be mapped from arrays or objects.', 1); |
||
80 | } |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * Maps the model from an array of data. |
||
85 | * |
||
86 | * @param array<string, mixed> $source |
||
87 | * |
||
88 | * @return T |
||
89 | */ |
||
90 | protected function fromArray(array $source) |
||
91 | { |
||
92 | $model = $this->newInstance(); |
||
93 | foreach ($source as $key => $value) { |
||
94 | $this->setProperty($model, $key, $value); |
||
95 | } |
||
96 | |||
97 | return $model; |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Maps a model from an Object of data |
||
102 | * |
||
103 | * @param object $source |
||
104 | * |
||
105 | * @return T |
||
106 | */ |
||
107 | protected function fromObject($source) |
||
108 | { |
||
109 | $vars = get_object_vars($source); |
||
110 | |||
111 | return $this->fromArray($vars); |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * Construct an instance of the model |
||
116 | |||
117 | * |
||
118 | * @return T |
||
119 | */ |
||
120 | protected function newInstance() |
||
121 | { |
||
122 | $class = $this->model; |
||
123 | try { |
||
124 | /** @var T */ |
||
125 | $instance = empty($this->constructorArgs) |
||
126 | ? new $class() |
||
127 | : new $class(...$this->constructorArgs); |
||
128 | } catch (Throwable $th) { |
||
129 | throw new Exception("Failed to construct model, {$th->getMessage()}", 1); |
||
130 | } |
||
131 | |||
132 | return $instance; |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Sets a property to the current model |
||
137 | * |
||
138 | * @param T $model |
||
139 | * @param string $property |
||
140 | * @param mixed $value |
||
141 | * |
||
142 | * @return T |
||
143 | */ |
||
144 | protected function setProperty($model, string $property, $value) |
||
145 | { |
||
146 | $property = $this->normaliseProperty($property); |
||
147 | |||
148 | // Attempt to set. |
||
149 | try { |
||
150 | switch (true) { |
||
151 | case method_exists($model, $this->generateSetterMethod($property)): |
||
0 ignored issues
–
show
method_exists($model, $t...etterMethod($property)) is not reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||
152 | $method = $this->generateSetterMethod($property); |
||
153 | $model->$method($value); |
||
154 | break; |
||
155 | |||
156 | case method_exists($model, $this->generateSetterMethod($property, true)): |
||
157 | $method = $this->generateSetterMethod($property, true); |
||
158 | $model->$method($value); |
||
159 | break; |
||
160 | |||
161 | default: |
||
162 | $model->$property = $value; |
||
163 | break; |
||
164 | } |
||
165 | } catch (Throwable $th) { |
||
166 | throw new Exception(sprintf('Failed to set %s of %s model, %s', $property, get_class($model), $th->getMessage()), 1); |
||
167 | } |
||
168 | |||
169 | return $model; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Normalises a property |
||
174 | * |
||
175 | * @param string $property |
||
176 | * |
||
177 | * @return string |
||
178 | */ |
||
179 | protected function normaliseProperty(string $property): string |
||
180 | { |
||
181 | return trim( |
||
182 | preg_replace('/[^a-z0-9]+/', '_', strtolower($property)) ?: '' |
||
183 | ); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Generates a generic setter method using either underscore [set_property()] or PSR2 style [setProperty()] |
||
188 | * |
||
189 | * @param string $property |
||
190 | * @param bool $underscore |
||
191 | * |
||
192 | * @return string |
||
193 | */ |
||
194 | protected function generateSetterMethod(string $property, bool $underscore = false): string |
||
195 | { |
||
196 | return $underscore |
||
197 | ? "set_{$property}" |
||
198 | : 'set' . ucfirst($property); |
||
199 | } |
||
200 | } |
||
201 |