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
Push — master ( bb70a8...e56b46 )
by Cees-Jan
03:55
created

Hydrator::hydrateFQCN()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

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