GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#13)
by Cees-Jan
06:43
created

Hydrator::hydrateApplyAnnotations()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 13
Ratio 100 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 13
loc 13
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 2
crap 3
1
<?php declare(strict_types=1);
2
3
namespace ApiClients\Foundation\Hydrator;
4
5
use ApiClients\Foundation\Hydrator\Annotations\EmptyResource;
6
use ApiClients\Foundation\Resource\EmptyResourceInterface;
7
use ApiClients\Foundation\Resource\ResourceInterface;
8
use ApiClients\Tools\CommandBus\CommandBus;
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 Interop\Container\ContainerInterface;
15
use React\EventLoop\LoopInterface;
16
use ReflectionClass;
17
use RecursiveDirectoryIterator;
18
use RecursiveIteratorIterator;
19
use ReflectionClass;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Cannot use ReflectionClass as ReflectionClass because the name is already in use
Loading history...
20
use Zend\Hydrator\HydratorInterface;
21
22
class Hydrator
23
{
24
    /**
25
     * @var ContainerInterface
26
     */
27
    protected $container;
28
29
    /**
30
     * @var array
31
     */
32
    protected $options;
33
34
    /**
35
     * @var array
36
     */
37
    protected $hydrators = [];
38
39
    /**
40
     * @var array
41
     */
42
    protected $annotations = [];
43
44
    /**
45
     * @var HandlerInterface[]
46
     */
47
    protected $annotationHandlers = [];
48
49
    /**
50
     * @var Reader
51
     */
52
    protected $annotationReader;
53
54
    /**
55
     * @var array
56
     */
57 7
    protected $classProperties = [];
58
59 7
    /**
60 7
     * @param ContainerInterface $container
61
     * @param array $options
62 7
     */
63 7
    public function __construct(ContainerInterface $container, array $options)
64 7
    {
65
        $this->container = $container;
66 1
        $this->options = $options;
67
68 1
        $reader = new AnnotationReader();
69
        if (isset($this->options[Options::ANNOTATION_CACHE]) &&
70
            $this->options[Options::ANNOTATION_CACHE] instanceof Cache
71 7
        ) {
72
            $reader = new CachedReader(
73 7
                $reader,
74 7
                $this->options[Options::ANNOTATION_CACHE]
75
            );
76 7
        }
77
        $this->annotationReader = $reader;
78 7
79
        $this->setUpAnnotations();
80
    }
81
82 7
    protected function setUpAnnotations()
83 7
    {
84
        if (!isset($this->options[Options::ANNOTATIONS])) {
85 7
            return;
86
        }
87 2
88
        foreach ($this->options[Options::ANNOTATIONS] as $annotationClass => $handler) {
89 2
            $this->annotationHandlers[$annotationClass] = new $handler($this);
90 2
        }
91
    }
92 2
93 2
    public function preheat(string $scanTarget, string $namespace)
94 2
    {
95
        $directory = new RecursiveDirectoryIterator($scanTarget);
96
        $directory = new RecursiveIteratorIterator($directory);
97 2
98 2
        foreach ($directory as $node) {
99 2
            if (!is_file($node->getPathname())) {
100
                continue;
101 2
            }
102
103 2
            $file = substr($node->getPathname(), strlen($scanTarget));
104
            $file = ltrim($file, DIRECTORY_SEPARATOR);
105
            $file = rtrim($file, '.php');
106
107 2
            $class = $namespace . '\\' . str_replace(DIRECTORY_SEPARATOR, '\\', $file);
108
109
            if (!class_exists($class)) {
110
                continue;
111 2
            }
112 2
113
            if (!is_subclass_of($class, ResourceInterface::class)) {
114 2
                continue;
115
            }
116
117
            $this->getHydrator($class);
118
            $this->annotationReader->getClassAnnotations(new ReflectionClass($class));
119
        }
120
    }
121 4
122
    /**
123 4
     * @param string $class
124 4
     * @param array $json
125
     * @return ResourceInterface
126
     */
127 4
    public function hydrate(string $class, array $json): ResourceInterface
128 4
    {
129
        $fullClassName = implode(
130
            '\\',
131 4
            [
132
                $this->options[Options::NAMESPACE],
133
                $this->options[Options::NAMESPACE_SUFFIX],
134
                $class,
135
            ]
136
        );
137
        return $this->hydrateFQCN($fullClassName, $json);
138
    }
139 4
140
    /**
141 4
     * @param string $class
142 4
     * @param array $json
143 4
     * @return ResourceInterface
144 4
     */
145 4
    public function hydrateFQCN(string $class, array $json): ResourceInterface
146 4
    {
147
        $class = $this->getEmptyOrResource($class, $json);
148
        $hydrator = $this->getHydrator($class);
149
        $object = new $class($this->container->get(LoopInterface::class), $this->container->get(CommandBus::class));
150
        $json = $this->hydrateApplyAnnotations($json, $object);
151
        $json = $this->ensureMissingValuesAreNull($json, $class);
152
        $resource = $hydrator->hydrate($json, $object);
153
        return $resource;
154 4
    }
155
156 4
    /**
157 4
     * @param array $json
158 4
     * @param ResourceInterface $object
159 4
     * @return array
160
     */
161
    protected function hydrateApplyAnnotations(array $json, ResourceInterface $object): array
162 4
    {
163
        foreach ($this->annotationHandlers as $annotationClass => $handler) {
164
            $annotation = $this->getAnnotation($object, $annotationClass);
165 4
            if ($annotation === null) {
166
                continue;
167
            }
168 4
169
            $json = $handler->hydrate($annotation, $json, $object);
170 4
        }
171 4
172
        return $json;
173
    }
174 4
175 4
    /**
176 4
     * Ensure all properties expected by resource are available
177
     *
178
     * @param array $json
179 4
     * @param string $class
180
     * @return array
181
     */
182
    protected function ensureMissingValuesAreNull(array $json, string $class): array
183
    {
184 4
        foreach ($this->getReflectionClassProperties($class) as $key) {
185 4
            if (isset($json[$key])) {
186 4
                continue;
187
            }
188
189 4
            $json[$key] = null;
190
        }
191
192
        return $json;
193 4
    }
194
195
    /**
196
     * @param string $class
197
     * @return string[]
198
     */
199
    protected function getReflectionClassProperties(string $class): array
200
    {
201 2
        if (isset($this->classProperties[$class])) {
202
            return $this->classProperties[$class];
203 2
        }
204 2
205
        $this->classProperties[$class] = [];
206
        foreach ((new ReflectionClass($class))->getProperties() as $property) {
207 2
            $this->classProperties[$class][] = (string)$property->getName();
208 2
        }
209
        return $this->classProperties[$class];
210
    }
211 2
212
    protected function getEmptyOrResource(string $class, array $json): string
213
    {
214
        if (count($json) > 0) {
215
            return $class;
216
        }
217
218
        $annotation = $this->getAnnotation(
219
            new $class($this->container->get(LoopInterface::class), $this->container->get(CommandBus::class)),
220 2
            EmptyResource::class
221
        );
222 2
223 2
        if (!($annotation instanceof EmptyResource)) {
224
            return $class;
225
        }
226 2
227 2
        $emptyClass = $this->options[Options::NAMESPACE] .
228 2
            '\\' .
229
            $this->options[Options::NAMESPACE_SUFFIX] .
230
            '\\' .
231
            $annotation->getEmptyReplacement();
232
233
        if (!class_exists($emptyClass)) {
234
            return $class;
235
        }
236 2
237
        return $emptyClass;
238 2
    }
239 2
240 2
    /**
241 2
     * @param string $class
242
     * @param ResourceInterface $object
243
     * @return array
244 2
     */
245
    public function extract(string $class, ResourceInterface $object): array
246
    {
247 2
        $fullClassName = implode(
248
            '\\',
249
            [
250
                $this->options[Options::NAMESPACE],
251
                $this->options[Options::NAMESPACE_SUFFIX],
252
                $class,
253
            ]
254
        );
255 4
        return $this->extractFQCN($fullClassName, $object);
256
    }
257 4
258 4
    /**
259 3
     * Takes a fully qualified class name and extracts the data for that class from the given $object
260
     * @param string $class
261
     * @param ResourceInterface $object
262 4
     * @return array
263 4
     */
264
    public function extractFQCN(string $class, ResourceInterface $object): array
265
    {
266 4
        if ($object instanceof EmptyResourceInterface) {
267 4
            return [];
268
        }
269
270
        $json = $this->getHydrator($class)->extract($object);
271
        $json = $this->extractApplyAnnotations($object, $json);
272
        return $json;
273
    }
274
275 4
    /**
276
     * @param array $json
277 4
     * @param ResourceInterface $object
278
     * @return array
279
     */
280
    protected function extractApplyAnnotations(ResourceInterface $object, array $json): array
281 4
    {
282 4
        foreach ($this->annotationHandlers as $annotationClass => $handler) {
283 4
            $annotation = $this->getAnnotation($object, $annotationClass);
284
            if ($annotation === null) {
285
                continue;
286
            }
287
288 4
            $json = $handler->extract($annotation, $object, $json);
289 4
        }
290
291 4
        return $json;
292
    }
293
294 4
    /**
295
     * @param ResourceInterface $object
296 4
     * @param string $annotationClass
297 4
     * @return null|AnnotationInterface
298
     */
299
    protected function getAnnotation(ResourceInterface $object, string $annotationClass)
300 4
    {
301
        $class = get_class($object);
302
        if (isset($this->annotations[$class][$annotationClass])) {
303
            return $this->annotations[$class][$annotationClass];
304
        }
305
306
        if (!isset($this->annotations[$class])) {
307
            $this->annotations[$class] = [];
308 1
        }
309
310 1
        $this->annotations[$class][$annotationClass] = $this->recursivelyGetAnnotation($class, $annotationClass);
311
        return $this->annotations[$class][$annotationClass];
312 1
    }
313
314
    /**
315
     * @param string $class
316
     * @param string $annotationClass
317
     * @return null|AnnotationInterface
318
     */
319
    protected function recursivelyGetAnnotation(string $class, string $annotationClass)
320
    {
321
        if (!class_exists($class)) {
322
            return null;
323 6
        }
324
325 6
        $annotation = $this->annotationReader
326 4
            ->getClassAnnotation(
327
                new ReflectionClass($class),
328
                $annotationClass
329 6
            )
330 6
        ;
331 6
332
        if ($annotation !== null &&
333 6
            get_class($annotation) === $annotationClass
334 6
        ) {
335
            return $annotation;
336 6
        }
337 6
338
        $parentClass = get_parent_class($class);
339 6
340
        if ($parentClass === false || !class_exists($parentClass)) {
341
            return null;
342
        }
343
344
        return $this->recursivelyGetAnnotation($parentClass, $annotationClass);
345
    }
346
347
    /**
348
     * @param string $resource
349
     * @param ResourceInterface $object
350
     * @return ResourceInterface
351
     */
352
    public function buildAsyncFromSync(string $resource, ResourceInterface $object): ResourceInterface
353
    {
354
        return $this->hydrateFQCN(
355
            $this->options[Options::NAMESPACE] . '\\Async\\' . $resource,
356
            $this->extractFQCN(
357
                $this->options[Options::NAMESPACE] . '\\Sync\\' . $resource,
358
                $object
359
            )
360
        );
361
    }
362
363
    /**
364
     * @param string $class
365
     * @return HydratorInterface
366
     */
367
    protected function getHydrator(string $class): HydratorInterface
368
    {
369
        if (isset($this->hydrators[$class])) {
370
            return $this->hydrators[$class];
371
        }
372
373
        $config = new Configuration($class);
374
        if (isset($this->options[Options::RESOURCE_CACHE_DIR])) {
375
            $config->setGeneratedClassesTargetDir($this->options[Options::RESOURCE_CACHE_DIR]);
376
        }
377
        if (isset($this->options[Options::RESOURCE_NAMESPACE])) {
378
            $config->setGeneratedClassesNamespace($this->options[Options::RESOURCE_NAMESPACE]);
379
        }
380
        $hydrator = $config->createFactory()->getHydratorClass();
381
        $this->hydrators[$class] = new $hydrator;
382
383
        return $this->hydrators[$class];
384
    }
385
}
386