This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php declare(strict_types=1); |
||
2 | |||
3 | namespace ApiClients\Foundation\Hydrator; |
||
4 | |||
5 | use ApiClients\Foundation\Hydrator\Annotation\EmptyResource; |
||
6 | use ApiClients\Foundation\Resource\EmptyResourceInterface; |
||
7 | use ApiClients\Foundation\Resource\ResourceInterface; |
||
8 | use ApiClients\Tools\CommandBus\CommandBusInterface; |
||
9 | use Doctrine\Common\Annotations\AnnotationReader; |
||
10 | use Doctrine\Common\Annotations\CachedReader; |
||
11 | use Doctrine\Common\Annotations\Reader; |
||
12 | use Doctrine\Common\Cache\Cache; |
||
13 | use GeneratedHydrator\Configuration; |
||
14 | use React\EventLoop\LoopInterface; |
||
15 | use RecursiveDirectoryIterator; |
||
16 | use RecursiveIteratorIterator; |
||
17 | use ReflectionClass; |
||
18 | use Zend\Hydrator\HydratorInterface; |
||
19 | |||
20 | class Hydrator |
||
21 | { |
||
22 | /** |
||
23 | * @var LoopInterface |
||
24 | */ |
||
25 | protected $loop; |
||
26 | |||
27 | /** |
||
28 | * @var CommandBusInterface |
||
29 | */ |
||
30 | protected $commandBus; |
||
31 | |||
32 | /** |
||
33 | * @var array |
||
34 | */ |
||
35 | protected $options; |
||
36 | |||
37 | /** |
||
38 | * @var array |
||
39 | */ |
||
40 | protected $hydrators = []; |
||
41 | |||
42 | /** |
||
43 | * @var array |
||
44 | */ |
||
45 | protected $annotations = []; |
||
46 | |||
47 | /** |
||
48 | * @var HandlerInterface[] |
||
49 | */ |
||
50 | protected $annotationHandlers = []; |
||
51 | |||
52 | /** |
||
53 | * @var Reader |
||
54 | */ |
||
55 | protected $annotationReader; |
||
56 | |||
57 | /** |
||
58 | * @var array |
||
59 | */ |
||
60 | protected $classProperties = []; |
||
61 | |||
62 | /** |
||
63 | * @param LoopInterface $loop |
||
64 | * @param CommandBusInterface $commandBus |
||
65 | * @param array $options |
||
66 | */ |
||
67 | 9 | public function __construct(LoopInterface $loop, CommandBusInterface $commandBus, array $options) |
|
68 | { |
||
69 | 9 | $this->loop = $loop; |
|
70 | 9 | $this->commandBus = $commandBus; |
|
71 | 9 | $this->options = $options; |
|
72 | |||
73 | 9 | $reader = new AnnotationReader(); |
|
74 | 9 | if (isset($this->options[Options::ANNOTATION_CACHE]) && |
|
75 | 9 | $this->options[Options::ANNOTATION_CACHE] instanceof Cache |
|
76 | ) { |
||
77 | 1 | $reader = new CachedReader( |
|
78 | 1 | $reader, |
|
79 | 1 | $this->options[Options::ANNOTATION_CACHE] |
|
80 | ); |
||
81 | } |
||
82 | 9 | $this->annotationReader = $reader; |
|
83 | |||
84 | 9 | $this->setUpAnnotations(); |
|
85 | 9 | } |
|
86 | |||
87 | 1 | public function preheat(string $scanTarget, string $namespace): void |
|
88 | { |
||
89 | 1 | $directory = new RecursiveDirectoryIterator($scanTarget); |
|
90 | 1 | $directory = new RecursiveIteratorIterator($directory); |
|
91 | |||
92 | 1 | foreach ($directory as $node) { |
|
93 | 1 | if (!\is_file($node->getPathname())) { |
|
94 | 1 | continue; |
|
95 | } |
||
96 | |||
97 | 1 | $file = \substr($node->getPathname(), \strlen($scanTarget)); |
|
98 | 1 | $file = \ltrim($file, \DIRECTORY_SEPARATOR); |
|
99 | 1 | $file = \rtrim($file, '.php'); |
|
100 | |||
101 | 1 | $class = $namespace . '\\' . \str_replace(\DIRECTORY_SEPARATOR, '\\', $file); |
|
102 | |||
103 | 1 | if (!\class_exists($class)) { |
|
104 | continue; |
||
105 | } |
||
106 | |||
107 | 1 | if (!\is_subclass_of($class, ResourceInterface::class)) { |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
108 | continue; |
||
109 | } |
||
110 | |||
111 | 1 | $this->getHydrator($class); |
|
112 | 1 | $this->annotationReader->getClassAnnotations(new ReflectionClass($class)); |
|
113 | } |
||
114 | 1 | } |
|
115 | |||
116 | /** |
||
117 | * @param string $class |
||
118 | * @param array $json |
||
119 | * @return ResourceInterface |
||
120 | */ |
||
121 | 5 | View Code Duplication | public function hydrate(string $class, array $json): ResourceInterface |
0 ignored issues
–
show
This method seems to be duplicated in your project.
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. ![]() |
|||
122 | { |
||
123 | 5 | $fullClassName = \implode( |
|
124 | 5 | '\\', |
|
125 | [ |
||
126 | $this->options[Options::NAMESPACE], |
||
127 | 5 | $this->options[Options::NAMESPACE_SUFFIX], |
|
128 | 5 | $class, |
|
129 | ] |
||
130 | ); |
||
131 | |||
132 | 5 | return $this->hydrateFQCN($fullClassName, $json); |
|
133 | } |
||
134 | |||
135 | /** |
||
136 | * @param string $class |
||
137 | * @param array $json |
||
138 | * @return ResourceInterface |
||
139 | */ |
||
140 | 7 | public function hydrateFQCN(string $class, array $json): ResourceInterface |
|
141 | { |
||
142 | 7 | $class = $this->getEmptyOrResource($class, $json); |
|
143 | 7 | $hydrator = $this->getHydrator($class); |
|
144 | 6 | $object = new $class( |
|
145 | 6 | $this->loop, |
|
146 | 6 | $this->commandBus |
|
147 | ); |
||
148 | 6 | $json = $this->hydrateApplyAnnotations($json, $object); |
|
149 | 6 | $json = $this->ensureMissingValuesAreNull($json, $class); |
|
150 | 6 | $resource = $hydrator->hydrate($json, $object); |
|
151 | |||
152 | 6 | return $resource; |
|
153 | } |
||
154 | |||
155 | /** |
||
156 | * @param string $class |
||
157 | * @param ResourceInterface $object |
||
158 | * @return array |
||
159 | */ |
||
160 | 2 | View Code Duplication | public function extract(string $class, ResourceInterface $object): array |
0 ignored issues
–
show
This method seems to be duplicated in your project.
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. ![]() |
|||
161 | { |
||
162 | 2 | $fullClassName = \implode( |
|
163 | 2 | '\\', |
|
164 | [ |
||
165 | $this->options[Options::NAMESPACE], |
||
166 | 2 | $this->options[Options::NAMESPACE_SUFFIX], |
|
167 | 2 | $class, |
|
168 | ] |
||
169 | ); |
||
170 | |||
171 | 2 | return $this->extractFQCN($fullClassName, $object); |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * Takes a fully qualified class name and extracts the data for that class from the given $object. |
||
176 | * @param string $class |
||
177 | * @param ResourceInterface $object |
||
178 | * @return array |
||
179 | */ |
||
180 | 3 | public function extractFQCN(string $class, ResourceInterface $object): array |
|
181 | { |
||
182 | 3 | if ($object instanceof EmptyResourceInterface) { |
|
183 | 2 | return []; |
|
184 | } |
||
185 | |||
186 | 3 | $json = $this->getHydrator($class)->extract($object); |
|
187 | 3 | unset($json['loop'], $json['commandBus']); |
|
188 | 3 | $json = $this->extractApplyAnnotations($object, $json); |
|
189 | |||
190 | 3 | return $json; |
|
191 | } |
||
192 | |||
193 | /** |
||
194 | * @param string $resource |
||
195 | * @param ResourceInterface $object |
||
196 | * @return ResourceInterface |
||
197 | */ |
||
198 | 1 | View Code Duplication | public function buildAsyncFromSync(string $resource, ResourceInterface $object): ResourceInterface |
0 ignored issues
–
show
This method seems to be duplicated in your project.
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. ![]() |
|||
199 | { |
||
200 | 1 | return $this->hydrateFQCN( |
|
201 | $this->options[Options::NAMESPACE] . '\\Async\\' . $resource, |
||
202 | 1 | $this->extractFQCN( |
|
203 | $this->options[Options::NAMESPACE] . '\\Sync\\' . $resource, |
||
204 | $object |
||
205 | ) |
||
206 | ); |
||
207 | } |
||
208 | |||
209 | /** |
||
210 | * @param string $resource |
||
211 | * @param ResourceInterface $object |
||
212 | * @return ResourceInterface |
||
213 | */ |
||
214 | View Code Duplication | public function buildSyncFromAsync(string $resource, ResourceInterface $object): ResourceInterface |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
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. ![]() |
|||
215 | { |
||
216 | return $this->hydrateFQCN( |
||
217 | $this->options[Options::NAMESPACE] . '\\Sync\\' . $resource, |
||
218 | $this->extractFQCN( |
||
219 | $this->options[Options::NAMESPACE] . '\\Async\\' . $resource, |
||
220 | $object |
||
221 | ) |
||
222 | ); |
||
223 | } |
||
224 | |||
225 | 9 | protected function setUpAnnotations(): void |
|
226 | { |
||
227 | 9 | if (!isset($this->options[Options::ANNOTATIONS])) { |
|
228 | return; |
||
229 | } |
||
230 | |||
231 | 9 | foreach ($this->options[Options::ANNOTATIONS] as $annotationClass => $handler) { |
|
232 | 9 | $this->annotationHandlers[$annotationClass] = new $handler($this); |
|
233 | } |
||
234 | 9 | } |
|
235 | |||
236 | /** |
||
237 | * @param array $json |
||
238 | * @param ResourceInterface $object |
||
239 | * @return array |
||
240 | */ |
||
241 | 6 | View Code Duplication | protected function hydrateApplyAnnotations(array $json, ResourceInterface $object): array |
0 ignored issues
–
show
This method seems to be duplicated in your project.
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. ![]() |
|||
242 | { |
||
243 | 6 | foreach ($this->annotationHandlers as $annotationClass => $handler) { |
|
244 | 6 | $annotation = $this->getAnnotation($object, $annotationClass); |
|
245 | 6 | if ($annotation === null) { |
|
246 | 5 | continue; |
|
247 | } |
||
248 | |||
249 | 6 | $json = $handler->hydrate($annotation, $json, $object); |
|
250 | } |
||
251 | |||
252 | 6 | return $json; |
|
253 | } |
||
254 | |||
255 | /** |
||
256 | * Ensure all properties expected by resource are available. |
||
257 | * |
||
258 | * @param array $json |
||
259 | * @param string $class |
||
260 | * @return array |
||
261 | */ |
||
262 | 6 | protected function ensureMissingValuesAreNull(array $json, string $class): array |
|
263 | { |
||
264 | 6 | foreach ($this->getReflectionClassProperties($class) as $key) { |
|
265 | 6 | if (isset($json[$key])) { |
|
266 | 6 | continue; |
|
267 | } |
||
268 | |||
269 | 2 | $json[$key] = null; |
|
270 | } |
||
271 | |||
272 | 6 | return $json; |
|
273 | } |
||
274 | |||
275 | /** |
||
276 | * @param string $class |
||
277 | * @return string[] |
||
278 | */ |
||
279 | 6 | protected function getReflectionClassProperties(string $class): array |
|
280 | { |
||
281 | 6 | if (isset($this->classProperties[$class])) { |
|
282 | 5 | return $this->classProperties[$class]; |
|
283 | } |
||
284 | |||
285 | 6 | $this->classProperties[$class] = []; |
|
286 | 6 | foreach ((new ReflectionClass($class))->getProperties() as $property) { |
|
287 | 6 | $this->classProperties[$class][] = (string)$property->getName(); |
|
288 | } |
||
289 | |||
290 | 6 | return $this->classProperties[$class]; |
|
291 | } |
||
292 | |||
293 | 7 | protected function getEmptyOrResource(string $class, array $json): string |
|
294 | { |
||
295 | 7 | if (\count($json) > 0) { |
|
296 | 7 | return $class; |
|
297 | } |
||
298 | |||
299 | 5 | $annotation = $this->getAnnotation( |
|
300 | 5 | new $class( |
|
301 | 5 | $this->loop, |
|
302 | 5 | $this->commandBus |
|
303 | ), |
||
304 | 5 | EmptyResource::class |
|
305 | ); |
||
306 | |||
307 | 5 | if (!($annotation instanceof EmptyResource)) { |
|
308 | return $class; |
||
309 | } |
||
310 | |||
311 | $emptyClass = $this->options[Options::NAMESPACE] . |
||
312 | 5 | '\\' . |
|
313 | 5 | $this->options[Options::NAMESPACE_SUFFIX] . |
|
314 | 5 | '\\' . |
|
315 | $annotation->getEmptyReplacement(); |
||
316 | |||
317 | 5 | if (!\class_exists($emptyClass)) { |
|
318 | return $class; |
||
319 | } |
||
320 | |||
321 | 5 | return $emptyClass; |
|
322 | } |
||
323 | |||
324 | /** |
||
325 | * @param array $json |
||
326 | * @param ResourceInterface $object |
||
327 | * @return array |
||
328 | */ |
||
329 | 3 | View Code Duplication | protected function extractApplyAnnotations(ResourceInterface $object, array $json): array |
0 ignored issues
–
show
This method seems to be duplicated in your project.
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. ![]() |
|||
330 | { |
||
331 | 3 | foreach ($this->annotationHandlers as $annotationClass => $handler) { |
|
332 | 3 | $annotation = $this->getAnnotation($object, $annotationClass); |
|
333 | 3 | if ($annotation === null) { |
|
334 | 2 | continue; |
|
335 | } |
||
336 | |||
337 | 3 | $json = $handler->extract($annotation, $object, $json); |
|
338 | } |
||
339 | |||
340 | 3 | return $json; |
|
341 | } |
||
342 | |||
343 | /** |
||
344 | * @param ResourceInterface $object |
||
345 | * @param string $annotationClass |
||
346 | * @return null|AnnotationInterface |
||
347 | */ |
||
348 | 6 | protected function getAnnotation(ResourceInterface $object, string $annotationClass) |
|
349 | { |
||
350 | 6 | $class = \get_class($object); |
|
351 | 6 | if (isset($this->annotations[$class][$annotationClass])) { |
|
352 | 4 | return $this->annotations[$class][$annotationClass]; |
|
353 | } |
||
354 | |||
355 | 6 | if (!isset($this->annotations[$class])) { |
|
356 | 6 | $this->annotations[$class] = []; |
|
357 | } |
||
358 | |||
359 | 6 | $this->annotations[$class][$annotationClass] = $this->recursivelyGetAnnotation($class, $annotationClass); |
|
360 | |||
361 | 6 | return $this->annotations[$class][$annotationClass]; |
|
362 | } |
||
363 | |||
364 | /** |
||
365 | * @param string $class |
||
366 | * @param string $annotationClass |
||
367 | * @return null|AnnotationInterface |
||
368 | */ |
||
369 | 6 | protected function recursivelyGetAnnotation(string $class, string $annotationClass) |
|
370 | { |
||
371 | 6 | if (!\class_exists($class)) { |
|
372 | return; |
||
373 | } |
||
374 | |||
375 | 6 | $annotation = $this->annotationReader |
|
376 | 6 | ->getClassAnnotation( |
|
377 | 6 | new ReflectionClass($class), |
|
378 | $annotationClass |
||
379 | ) |
||
380 | ; |
||
381 | |||
382 | 6 | if ($annotation !== null && |
|
383 | 6 | \get_class($annotation) === $annotationClass |
|
384 | ) { |
||
385 | 6 | return $annotation; |
|
386 | } |
||
387 | |||
388 | 6 | $parentClass = \get_parent_class($class); |
|
389 | |||
390 | 6 | if ($parentClass === false || !\class_exists($parentClass)) { |
|
391 | 5 | return; |
|
392 | } |
||
393 | |||
394 | 6 | return $this->recursivelyGetAnnotation($parentClass, $annotationClass); |
|
395 | } |
||
396 | |||
397 | /** |
||
398 | * @param string $class |
||
399 | * @return HydratorInterface |
||
400 | */ |
||
401 | 8 | protected function getHydrator(string $class): HydratorInterface |
|
402 | { |
||
403 | 8 | if (isset($this->hydrators[$class])) { |
|
404 | 6 | return $this->hydrators[$class]; |
|
405 | } |
||
406 | |||
407 | if (\strpos($class, $this->options[Options::NAMESPACE]) !== 0) { |
||
408 | throw OutsideNamespaceException::create($class, $this->options[Options::NAMESPACE]); |
||
409 | } |
||
410 | |||
411 | 7 | $config = new Configuration($class); |
|
412 | 7 | if (isset($this->options[Options::RESOURCE_CACHE_DIR])) { |
|
413 | 7 | $config->setGeneratedClassesTargetDir($this->options[Options::RESOURCE_CACHE_DIR]); |
|
414 | } |
||
415 | 7 | if (isset($this->options[Options::RESOURCE_NAMESPACE])) { |
|
416 | 7 | $config->setGeneratedClassesNamespace($this->options[Options::RESOURCE_NAMESPACE]); |
|
417 | } |
||
418 | 7 | $hydrator = $config->createFactory()->getHydratorClass(); |
|
419 | 7 | $this->hydrators[$class] = new $hydrator(); |
|
420 | |||
421 | 7 | return $this->hydrators[$class]; |
|
422 | } |
||
423 | } |
||
424 |