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
03:40 queued 01:42
created

Hydrator::hydrateFQCN()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1.0527

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 8
cp 0.625
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 2
crap 1.0527
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 RecursiveDirectoryIterator;
17
use RecursiveIteratorIterator;
18
use Zend\Hydrator\HydratorInterface;
19
20
class Hydrator
21
{
22
    /**
23
     * @var ContainerInterface
24
     */
25
    protected $container;
26
27
    /**
28
     * @var array
29
     */
30
    protected $options;
31
32
    /**
33
     * @var array
34
     */
35
    protected $hydrators = [];
36
37
    /**
38
     * @var array
39
     */
40
    protected $annotations = [];
41
42
    /**
43
     * @var HandlerInterface[]
44
     */
45
    protected $annotationHandlers = [];
46
47
    /**
48
     * @var Reader
49
     */
50
    protected $annotationReader;
51
52
    /**
53
     * @var array
54
     */
55
    protected $classProperties = [];
56
57
    /**
58
     * @param ContainerInterface $container
59
     * @param array $options
60
     */
61 9
    public function __construct(ContainerInterface $container, array $options)
62
    {
63 9
        $this->container = $container;
64 9
        $this->options = $options;
65
66 9
        $reader = new AnnotationReader();
67 9
        if (isset($this->options[Options::ANNOTATION_CACHE]) &&
68 9
            $this->options[Options::ANNOTATION_CACHE] instanceof Cache
69
        ) {
70 1
            $reader = new CachedReader(
71
                $reader,
72 1
                $this->options[Options::ANNOTATION_CACHE]
73
            );
74
        }
75 9
        $this->annotationReader = $reader;
76
77 9
        $this->setUpAnnotations();
78 9
    }
79
80 9
    protected function setUpAnnotations()
81
    {
82 9
        if (!isset($this->options[Options::ANNOTATIONS])) {
83
            return;
84
        }
85
86 9
        foreach ($this->options[Options::ANNOTATIONS] as $annotationClass => $handler) {
87 9
            $this->annotationHandlers[$annotationClass] = new $handler($this);
88
        }
89 9
    }
90
91 2
    public function preheat(string $scanTarget, string $namespace)
92
    {
93 2
        $directory = new RecursiveDirectoryIterator($scanTarget);
94 2
        $directory = new RecursiveIteratorIterator($directory);
95
96 2
        foreach ($directory as $node) {
97 2
            if (!is_file($node->getPathname())) {
98 2
                continue;
99
            }
100
101 2
            $file = substr($node->getPathname(), strlen($scanTarget));
102 2
            $file = ltrim($file, DIRECTORY_SEPARATOR);
103 2
            $file = rtrim($file, '.php');
104
105 2
            $class = $namespace . '\\' . str_replace(DIRECTORY_SEPARATOR, '\\', $file);
106
107 2
            if (!class_exists($class)) {
108
                continue;
109
            }
110
111 2
            if (!is_subclass_of($class, ResourceInterface::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \ApiClients\Foundation\R...esourceInterface::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
112
                continue;
113
            }
114
115 2
            $this->getHydrator($class);
116 2
            $this->annotationReader->getClassAnnotations(new ReflectionClass($class));
117
        }
118
    }
119
120
    /**
121
     * @param string $class
122
     * @param array $json
123
     * @return ResourceInterface
124
     */
125 3 View Code Duplication
    public function hydrate(string $class, array $json): ResourceInterface
0 ignored issues
show
Duplication introduced by
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.

Loading history...
126
    {
127 3
        $fullClassName = implode(
128 3
            '\\',
129
            [
130
                $this->options[Options::NAMESPACE],
131 3
                $this->options[Options::NAMESPACE_SUFFIX],
132 3
                $class,
133
            ]
134
        );
135 3
        return $this->hydrateFQCN($fullClassName, $json);
136
    }
137
138
    /**
139
     * @param string $class
140
     * @param array $json
141
     * @return ResourceInterface
142
     */
143 6
    public function hydrateFQCN(string $class, array $json): ResourceInterface
144
    {
145 6
        $class = $this->getEmptyOrResource($class, $json);
146 6
        $hydrator = $this->getHydrator($class);
147 6
        $object = new $class($this->container->get(LoopInterface::class), $this->container->get(CommandBus::class));
148 4
        $json = $this->hydrateApplyAnnotations($json, $object);
149
        $json = $this->ensureMissingValuesAreNull($json, $class);
150
        $resource = $hydrator->hydrate($json, $object);
151
        return $resource;
152
    }
153
154
    /**
155
     * @param array $json
156
     * @param ResourceInterface $object
157
     * @return array
158
     */
159 4 View Code Duplication
    protected function hydrateApplyAnnotations(array $json, ResourceInterface $object): array
0 ignored issues
show
Duplication introduced by
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.

Loading history...
160
    {
161 4
        foreach ($this->annotationHandlers as $annotationClass => $handler) {
162 4
            $annotation = $this->getAnnotation($object, $annotationClass);
163
            if ($annotation === null) {
164
                continue;
165
            }
166
167
            $json = $handler->hydrate($annotation, $json, $object);
168
        }
169
170
        return $json;
171
    }
172
173
    /**
174
     * Ensure all properties expected by resource are available
175
     *
176
     * @param array $json
177
     * @param string $class
178
     * @return array
179
     */
180
    protected function ensureMissingValuesAreNull(array $json, string $class): array
181
    {
182
        foreach ($this->getReflectionClassProperties($class) as $key) {
183
            if (isset($json[$key])) {
184
                continue;
185
            }
186
187
            $json[$key] = null;
188
        }
189
190
        return $json;
191
    }
192
193
    /**
194
     * @param string $class
195
     * @return string[]
196
     */
197
    protected function getReflectionClassProperties(string $class): array
198
    {
199
        if (isset($this->classProperties[$class])) {
200
            return $this->classProperties[$class];
201
        }
202
203
        $this->classProperties[$class] = [];
204
        foreach ((new ReflectionClass($class))->getProperties() as $property) {
205
            $this->classProperties[$class][] = (string)$property->getName();
206
        }
207
        return $this->classProperties[$class];
208
    }
209
210 6
    protected function getEmptyOrResource(string $class, array $json): string
211
    {
212 6
        if (count($json) > 0) {
213 6
            return $class;
214
        }
215
216
        $annotation = $this->getAnnotation(
217
            new $class($this->container->get(LoopInterface::class), $this->container->get(CommandBus::class)),
218
            EmptyResource::class
219
        );
220
221
        if (!($annotation instanceof EmptyResource)) {
222
            return $class;
223
        }
224
225
        $emptyClass = $this->options[Options::NAMESPACE] .
226
            '\\' .
227
            $this->options[Options::NAMESPACE_SUFFIX] .
228
            '\\' .
229
            $annotation->getEmptyReplacement();
230
231
        if (!class_exists($emptyClass)) {
232
            return $class;
233
        }
234
235
        return $emptyClass;
236
    }
237
238
    /**
239
     * @param string $class
240
     * @param ResourceInterface $object
241
     * @return array
242
     */
243 View Code Duplication
    public function extract(string $class, ResourceInterface $object): array
0 ignored issues
show
Duplication introduced by
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.

Loading history...
244
    {
245
        $fullClassName = implode(
246
            '\\',
247
            [
248
                $this->options[Options::NAMESPACE],
249
                $this->options[Options::NAMESPACE_SUFFIX],
250
                $class,
251
            ]
252
        );
253
        return $this->extractFQCN($fullClassName, $object);
254
    }
255
256
    /**
257
     * Takes a fully qualified class name and extracts the data for that class from the given $object
258
     * @param string $class
259
     * @param ResourceInterface $object
260
     * @return array
261
     */
262
    public function extractFQCN(string $class, ResourceInterface $object): array
263
    {
264
        if ($object instanceof EmptyResourceInterface) {
265
            return [];
266
        }
267
268
        $json = $this->getHydrator($class)->extract($object);
269
        $json = $this->extractApplyAnnotations($object, $json);
270
        return $json;
271
    }
272
273
    /**
274
     * @param array $json
275
     * @param ResourceInterface $object
276
     * @return array
277
     */
278 View Code Duplication
    protected function extractApplyAnnotations(ResourceInterface $object, array $json): array
0 ignored issues
show
Duplication introduced by
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.

Loading history...
279
    {
280
        foreach ($this->annotationHandlers as $annotationClass => $handler) {
281
            $annotation = $this->getAnnotation($object, $annotationClass);
282
            if ($annotation === null) {
283
                continue;
284
            }
285
286
            $json = $handler->extract($annotation, $object, $json);
287
        }
288
289
        return $json;
290
    }
291
292
    /**
293
     * @param ResourceInterface $object
294
     * @param string $annotationClass
295
     * @return null|AnnotationInterface
296
     */
297 4
    protected function getAnnotation(ResourceInterface $object, string $annotationClass)
298
    {
299 4
        $class = get_class($object);
300 4
        if (isset($this->annotations[$class][$annotationClass])) {
301
            return $this->annotations[$class][$annotationClass];
302
        }
303
304 4
        if (!isset($this->annotations[$class])) {
305 4
            $this->annotations[$class] = [];
306
        }
307
308 4
        $this->annotations[$class][$annotationClass] = $this->recursivelyGetAnnotation($class, $annotationClass);
309
        return $this->annotations[$class][$annotationClass];
310
    }
311
312
    /**
313
     * @param string $class
314
     * @param string $annotationClass
315
     * @return null|AnnotationInterface
316
     */
317 4
    protected function recursivelyGetAnnotation(string $class, string $annotationClass)
318
    {
319 4
        if (!class_exists($class)) {
320
            return null;
321
        }
322
323 4
        $annotation = $this->annotationReader
324 4
            ->getClassAnnotation(
325 4
                new ReflectionClass($class),
326
                $annotationClass
327
            )
328
        ;
329
330
        if ($annotation !== null &&
331
            get_class($annotation) === $annotationClass
332
        ) {
333
            return $annotation;
334
        }
335
336
        $parentClass = get_parent_class($class);
337
338
        if ($parentClass === false || !class_exists($parentClass)) {
339
            return null;
340
        }
341
342
        return $this->recursivelyGetAnnotation($parentClass, $annotationClass);
343
    }
344
345
    /**
346
     * @param string $resource
347
     * @param ResourceInterface $object
348
     * @return ResourceInterface
349
     */
350
    public function buildAsyncFromSync(string $resource, ResourceInterface $object): ResourceInterface
351
    {
352
        return $this->hydrateFQCN(
353
            $this->options[Options::NAMESPACE] . '\\Async\\' . $resource,
354
            $this->extractFQCN(
355
                $this->options[Options::NAMESPACE] . '\\Sync\\' . $resource,
356
                $object
357
            )
358
        );
359
    }
360
361
    /**
362
     * @param string $class
363
     * @return HydratorInterface
364
     */
365 8
    protected function getHydrator(string $class): HydratorInterface
366
    {
367 8
        if (isset($this->hydrators[$class])) {
368
            return $this->hydrators[$class];
369
        }
370
371 8
        $config = new Configuration($class);
372 8
        if (isset($this->options[Options::RESOURCE_CACHE_DIR])) {
373 8
            $config->setGeneratedClassesTargetDir($this->options[Options::RESOURCE_CACHE_DIR]);
374
        }
375 8
        if (isset($this->options[Options::RESOURCE_NAMESPACE])) {
376 8
            $config->setGeneratedClassesNamespace($this->options[Options::RESOURCE_NAMESPACE]);
377
        }
378 8
        $hydrator = $config->createFactory()->getHydratorClass();
379 8
        $this->hydrators[$class] = new $hydrator;
380
381 8
        return $this->hydrators[$class];
382
    }
383
}
384