1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace WyriHaximus\ApiClient\Transport; |
5
|
|
|
|
6
|
|
|
use Doctrine\Common\Annotations\AnnotationReader; |
7
|
|
|
use GeneratedHydrator\Configuration; |
8
|
|
|
use ReflectionClass; |
9
|
|
|
use WyriHaximus\ApiClient\Annotations\Collection; |
10
|
|
|
use WyriHaximus\ApiClient\Annotations\Nested; |
11
|
|
|
use WyriHaximus\ApiClient\Annotations\Rename; |
12
|
|
|
use WyriHaximus\ApiClient\Resource\ResourceInterface; |
13
|
|
|
use Zend\Hydrator\HydratorInterface; |
14
|
|
|
|
15
|
|
|
class Hydrator |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* @var array |
19
|
|
|
*/ |
20
|
|
|
protected $options; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @var Client |
24
|
|
|
*/ |
25
|
|
|
protected $transport; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var array |
29
|
|
|
*/ |
30
|
|
|
protected $hydrators = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var array |
34
|
|
|
*/ |
35
|
|
|
protected $annotations = []; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var AnnotationReader |
39
|
|
|
*/ |
40
|
|
|
protected $annotationReader; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @param Client $transport |
44
|
|
|
* @param array $options |
45
|
|
|
*/ |
46
|
17 |
|
public function __construct(Client $transport, array $options) |
47
|
|
|
{ |
48
|
17 |
|
$this->transport = $transport; |
49
|
17 |
|
$this->options = $options; |
50
|
17 |
|
$this->annotationReader = new AnnotationReader(); |
51
|
17 |
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @param string $class |
55
|
|
|
* @param array $json |
56
|
|
|
* @return ResourceInterface |
57
|
|
|
*/ |
58
|
5 |
|
public function hydrateFQCN(string $class, array $json): ResourceInterface |
59
|
|
|
{ |
60
|
5 |
|
$hydrator = $this->getHydrator($class); |
61
|
5 |
|
$object = $this->createObject($class); |
62
|
5 |
|
$json = $this->hydrateNestedResources($object, $json); |
63
|
5 |
|
$json = $this->hydrateCollectionResources($object, $json); |
64
|
5 |
|
$json = $this->hydrateRenameResourceProperties($object, $json); |
65
|
5 |
|
return $hydrator->hydrate($json, $object); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @param ResourceInterface $object |
70
|
|
|
* @param array $json |
71
|
|
|
* @return array |
72
|
|
|
*/ |
73
|
5 |
View Code Duplication |
protected function hydrateNestedResources(ResourceInterface $object, array $json) |
|
|
|
|
74
|
|
|
{ |
75
|
5 |
|
$annotation = $this->getAnnotation($object, Nested::class); |
76
|
|
|
|
77
|
5 |
|
if (!($annotation instanceof Nested)) { |
78
|
5 |
|
return $json; |
79
|
|
|
} |
80
|
|
|
|
81
|
5 |
|
foreach ($annotation->properties() as $property) { |
82
|
5 |
|
if ($json[$property] === null) { |
83
|
|
|
continue; |
84
|
|
|
} |
85
|
|
|
|
86
|
5 |
|
$json[$property] = $this->hydrate($annotation->get($property), $json[$property]); |
87
|
|
|
} |
88
|
|
|
|
89
|
5 |
|
return $json; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @param ResourceInterface $object |
94
|
|
|
* @param array $json |
95
|
|
|
* @return array |
96
|
|
|
*/ |
97
|
5 |
View Code Duplication |
protected function hydrateCollectionResources(ResourceInterface $object, array $json) |
|
|
|
|
98
|
|
|
{ |
99
|
5 |
|
$annotation = $this->getAnnotation($object, Collection::class); |
100
|
|
|
|
101
|
5 |
|
if (!($annotation instanceof Collection)) { |
102
|
5 |
|
return $json; |
103
|
|
|
} |
104
|
|
|
|
105
|
5 |
|
foreach ($annotation->properties() as $property) { |
106
|
5 |
|
$array = $json[$property]; |
107
|
5 |
|
$json[$property] = []; |
108
|
5 |
|
foreach ($array as $resource) { |
109
|
5 |
|
if ($resource === null) { |
110
|
|
|
continue; |
111
|
|
|
} |
112
|
|
|
|
113
|
5 |
|
$json[$property][] = $this->hydrate($annotation->get($property), $resource); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
5 |
|
return $json; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* @param ResourceInterface $object |
122
|
|
|
* @param array $json |
123
|
|
|
* @return array |
124
|
|
|
*/ |
125
|
5 |
View Code Duplication |
protected function hydrateRenameResourceProperties(ResourceInterface $object, array $json) |
|
|
|
|
126
|
|
|
{ |
127
|
5 |
|
$annotation = $this->getAnnotation($object, Rename::class); |
128
|
|
|
|
129
|
5 |
|
if (!($annotation instanceof Rename)) { |
130
|
5 |
|
return $json; |
131
|
|
|
} |
132
|
|
|
|
133
|
5 |
|
foreach ($annotation->properties() as $property) { |
134
|
5 |
|
$json[$property] = $json[$annotation->get($property)]; |
135
|
5 |
|
unset($json[$annotation->get($property)]); |
136
|
|
|
} |
137
|
|
|
|
138
|
5 |
|
return $json; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @param string $class |
143
|
|
|
* @param array $json |
144
|
|
|
* @return ResourceInterface |
145
|
|
|
*/ |
146
|
5 |
|
public function hydrate(string $class, array $json): ResourceInterface |
147
|
|
|
{ |
148
|
5 |
|
$fullClassName = $this->options['namespace'] . '\\' . $this->options['resource_namespace'] . '\\' . $class; |
149
|
5 |
|
return $this->hydrateFQCN($fullClassName, $json); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Takes a fully qualified class name and extracts the data for that class from the given $object |
154
|
|
|
* @param string $class |
155
|
|
|
* @param ResourceInterface $object |
156
|
|
|
* @return array |
157
|
|
|
*/ |
158
|
2 |
|
public function extractFQCN(string $class, ResourceInterface $object): array |
159
|
|
|
{ |
160
|
2 |
|
$json = $this->getHydrator($class)->extract($object); |
161
|
2 |
|
$json = $this->extractNestedResources($json, $object); |
162
|
2 |
|
$json = $this->extractCollectionResources($json, $object); |
163
|
2 |
|
$json = $this->extractRenameResourceProperties($json, $object); |
164
|
2 |
|
return $json; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @param array $json |
169
|
|
|
* @param ResourceInterface $object |
170
|
|
|
* @return array |
171
|
|
|
*/ |
172
|
2 |
View Code Duplication |
protected function extractNestedResources(array $json, ResourceInterface $object) |
|
|
|
|
173
|
|
|
{ |
174
|
2 |
|
$annotation = $this->getAnnotation($object, Nested::class); |
175
|
|
|
|
176
|
2 |
|
if (!($annotation instanceof Nested)) { |
177
|
2 |
|
return $json; |
178
|
|
|
} |
179
|
|
|
|
180
|
2 |
|
foreach ($annotation->properties() as $property) { |
181
|
2 |
|
$json[$property] = $this->extract($annotation->get($property), $json[$property]); |
182
|
|
|
} |
183
|
|
|
|
184
|
2 |
|
return $json; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @param array $json |
189
|
|
|
* @param ResourceInterface $object |
190
|
|
|
* @return array |
191
|
|
|
*/ |
192
|
2 |
View Code Duplication |
protected function extractCollectionResources(array $json, ResourceInterface $object) |
|
|
|
|
193
|
|
|
{ |
194
|
2 |
|
$annotation = $this->getAnnotation($object, Collection::class); |
195
|
|
|
|
196
|
2 |
|
if (!($annotation instanceof Collection)) { |
197
|
2 |
|
return $json; |
198
|
|
|
} |
199
|
|
|
|
200
|
2 |
|
foreach ($annotation->properties() as $property) { |
201
|
2 |
|
$array = $json[$property]; |
202
|
2 |
|
$json[$property] = []; |
203
|
2 |
|
foreach ($array as $resource) { |
204
|
2 |
|
$json[$property][] = $this->extract($annotation->get($property), $resource); |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
208
|
2 |
|
return $json; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param array $json |
213
|
|
|
* @param ResourceInterface $object |
214
|
|
|
* @return array |
215
|
|
|
*/ |
216
|
2 |
View Code Duplication |
protected function extractRenameResourceProperties(array $json, ResourceInterface $object) |
|
|
|
|
217
|
|
|
{ |
218
|
2 |
|
$annotation = $this->getAnnotation($object, Rename::class); |
219
|
|
|
|
220
|
2 |
|
if (!($annotation instanceof Rename)) { |
221
|
2 |
|
return $json; |
222
|
|
|
} |
223
|
|
|
|
224
|
2 |
|
foreach ($annotation->properties() as $property) { |
225
|
2 |
|
$json[$annotation->get($property)] = $json[$property]; |
226
|
2 |
|
unset($json[$property]); |
227
|
|
|
} |
228
|
|
|
|
229
|
2 |
|
return $json; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* @param ResourceInterface $object |
234
|
|
|
* @param string $annotationClass |
235
|
|
|
* @return null|object |
236
|
|
|
*/ |
237
|
5 |
|
protected function getAnnotation(ResourceInterface $object, string $annotationClass) |
238
|
|
|
{ |
239
|
5 |
|
$class = get_class($object); |
240
|
5 |
|
if (isset($this->annotations[$class][$annotationClass])) { |
241
|
1 |
|
return $this->annotations[$class][$annotationClass]; |
242
|
|
|
} |
243
|
|
|
|
244
|
5 |
|
if (!isset($this->annotations[$class])) { |
245
|
5 |
|
$this->annotations[$class] = []; |
246
|
|
|
} |
247
|
|
|
|
248
|
5 |
|
$this->annotations[$class][$annotationClass] = $this->annotationReader |
249
|
5 |
|
->getClassAnnotation( |
250
|
5 |
|
new ReflectionClass($object), |
251
|
|
|
$annotationClass |
252
|
|
|
) |
253
|
|
|
; |
254
|
|
|
|
255
|
5 |
|
if (get_class($this->annotations[$class][$annotationClass]) === $annotationClass) { |
256
|
|
|
return $this->annotations[$class][$annotationClass]; |
257
|
|
|
} |
258
|
|
|
|
259
|
5 |
|
$this->annotations[$class][$annotationClass] = $this->annotationReader |
260
|
5 |
|
->getClassAnnotation( |
261
|
5 |
|
new ReflectionClass(get_parent_class($object)), |
262
|
|
|
$annotationClass |
263
|
|
|
) |
264
|
|
|
; |
265
|
|
|
|
266
|
5 |
|
return $this->annotations[$class][$annotationClass]; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* @param string $class |
271
|
|
|
* @param ResourceInterface $object |
272
|
|
|
* @return array |
273
|
|
|
*/ |
274
|
2 |
|
public function extract(string $class, ResourceInterface $object): array |
275
|
|
|
{ |
276
|
2 |
|
$fullClassName = $this->options['namespace'] . '\\' . $this->options['resource_namespace'] . '\\' . $class; |
277
|
2 |
|
return $this->extractFQCN($fullClassName, $object); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* @param string $resource |
282
|
|
|
* @param ResourceInterface $object |
283
|
|
|
* @return ResourceInterface |
284
|
|
|
*/ |
285
|
1 |
|
public function buildAsyncFromSync(string $resource, ResourceInterface $object): ResourceInterface |
286
|
|
|
{ |
287
|
1 |
|
return $this->hydrateFQCN( |
288
|
1 |
|
$this->options['namespace'] . '\\Async\\' . $resource, |
289
|
1 |
|
$this->extractFQCN( |
290
|
1 |
|
$this->options['namespace'] . '\\Sync\\' . $resource, |
291
|
|
|
$object |
292
|
|
|
) |
293
|
|
|
); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* @param string $class |
298
|
|
|
* @return HydratorInterface |
299
|
|
|
*/ |
300
|
5 |
|
protected function getHydrator(string $class): HydratorInterface |
301
|
|
|
{ |
302
|
5 |
|
if (isset($this->hydrators[$class])) { |
303
|
5 |
|
return $this->hydrators[$class]; |
304
|
|
|
} |
305
|
|
|
|
306
|
5 |
|
$config = new Configuration($class); |
307
|
5 |
|
if (isset($this->options['resource_hydrator_cache_dir'])) { |
308
|
5 |
|
$config->setGeneratedClassesTargetDir($this->options['resource_hydrator_cache_dir']); |
309
|
|
|
} |
310
|
5 |
|
if (isset($this->options['resource_hydrator_namespace'])) { |
311
|
5 |
|
$config->setGeneratedClassesNamespace($this->options['resource_hydrator_namespace']); |
312
|
|
|
} |
313
|
5 |
|
$hydrator = $config->createFactory()->getHydratorClass(); |
314
|
5 |
|
$this->hydrators[$class] = new $hydrator; |
315
|
|
|
|
316
|
5 |
|
return $this->hydrators[$class]; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* @param string $class |
321
|
|
|
* @return ResourceInterface |
322
|
|
|
*/ |
323
|
5 |
|
protected function createObject(string $class): ResourceInterface |
324
|
|
|
{ |
325
|
5 |
|
$object = new $class(); |
326
|
5 |
|
$object->setTransport($this->transport); |
327
|
5 |
|
if (isset($this->options['setters'])) { |
328
|
|
|
foreach ($this->options['setters'] as $method => $argument) { |
329
|
|
|
if (!method_exists($object, $method)) { |
330
|
|
|
continue; |
331
|
|
|
} |
332
|
|
|
$object->$method($argument); |
333
|
|
|
} |
334
|
|
|
} |
335
|
5 |
|
return $object; |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.