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.

Hydrator::ensureMissingValuesAreNull()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
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
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...
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
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...
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
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...
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
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...
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
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...
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
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...
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
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...
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