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 ( 082543...ab5fae )
by Cees-Jan
10s
created

Hydrator::hydrate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 12
Ratio 100 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 12
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 7
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\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 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($this->container->get(LoopInterface::class), $this->container->get(CommandBus::class));
149 6
        $json = $this->hydrateApplyAnnotations($json, $object);
150 6
        $json = $this->ensureMissingValuesAreNull($json, $class);
151 6
        $resource = $hydrator->hydrate($json, $object);
152 6
        return $resource;
153
    }
154
155
    /**
156
     * @param array $json
157
     * @param ResourceInterface $object
158
     * @return array
159
     */
160 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...
161
    {
162 6
        foreach ($this->annotationHandlers as $annotationClass => $handler) {
163 6
            $annotation = $this->getAnnotation($object, $annotationClass);
164 6
            if ($annotation === null) {
165 5
                continue;
166
            }
167
168 6
            $json = $handler->hydrate($annotation, $json, $object);
169
        }
170
171 6
        return $json;
172
    }
173
174
    /**
175
     * Ensure all properties expected by resource are available
176
     *
177
     * @param array $json
178
     * @param string $class
179
     * @return array
180
     */
181 6
    protected function ensureMissingValuesAreNull(array $json, string $class): array
182
    {
183 6
        foreach ($this->getReflectionClassProperties($class) as $key) {
184 6
            if (isset($json[$key])) {
185 6
                continue;
186
            }
187
188 2
            $json[$key] = null;
189
        }
190
191 6
        return $json;
192
    }
193
194
    /**
195
     * @param string $class
196
     * @return string[]
197
     */
198 6
    protected function getReflectionClassProperties(string $class): array
199
    {
200 6
        if (isset($this->classProperties[$class])) {
201 5
            return $this->classProperties[$class];
202
        }
203
204 6
        $this->classProperties[$class] = [];
205 6
        foreach ((new ReflectionClass($class))->getProperties() as $property) {
206 6
            $this->classProperties[$class][] = (string)$property->getName();
207
        }
208 6
        return $this->classProperties[$class];
209
    }
210
211 6
    protected function getEmptyOrResource(string $class, array $json): string
212
    {
213 6
        if (count($json) > 0) {
214 6
            return $class;
215
        }
216
217 5
        $annotation = $this->getAnnotation(
218 5
            new $class($this->container->get(LoopInterface::class), $this->container->get(CommandBus::class)),
219 5
            EmptyResource::class
220
        );
221
222 5
        if (!($annotation instanceof EmptyResource)) {
223
            return $class;
224
        }
225
226
        $emptyClass = $this->options[Options::NAMESPACE] .
227 5
            '\\' .
228 5
            $this->options[Options::NAMESPACE_SUFFIX] .
229 5
            '\\' .
230
            $annotation->getEmptyReplacement();
231
232 5
        if (!class_exists($emptyClass)) {
233
            return $class;
234
        }
235
236 5
        return $emptyClass;
237
    }
238
239
    /**
240
     * @param string $class
241
     * @param ResourceInterface $object
242
     * @return array
243
     */
244 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...
245
    {
246 2
        $fullClassName = implode(
247 2
            '\\',
248
            [
249
                $this->options[Options::NAMESPACE],
250 2
                $this->options[Options::NAMESPACE_SUFFIX],
251 2
                $class,
252
            ]
253
        );
254 2
        return $this->extractFQCN($fullClassName, $object);
255
    }
256
257
    /**
258
     * Takes a fully qualified class name and extracts the data for that class from the given $object
259
     * @param string $class
260
     * @param ResourceInterface $object
261
     * @return array
262
     */
263 3
    public function extractFQCN(string $class, ResourceInterface $object): array
264
    {
265 3
        if ($object instanceof EmptyResourceInterface) {
266 2
            return [];
267
        }
268
269 3
        $json = $this->getHydrator($class)->extract($object);
270 3
        $json = $this->extractApplyAnnotations($object, $json);
271 3
        return $json;
272
    }
273
274
    /**
275
     * @param array $json
276
     * @param ResourceInterface $object
277
     * @return array
278
     */
279 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...
280
    {
281 3
        foreach ($this->annotationHandlers as $annotationClass => $handler) {
282 3
            $annotation = $this->getAnnotation($object, $annotationClass);
283 3
            if ($annotation === null) {
284 2
                continue;
285
            }
286
287 3
            $json = $handler->extract($annotation, $object, $json);
288
        }
289
290 3
        return $json;
291
    }
292
293
    /**
294
     * @param ResourceInterface $object
295
     * @param string $annotationClass
296
     * @return null|AnnotationInterface
297
     */
298 6
    protected function getAnnotation(ResourceInterface $object, string $annotationClass)
299
    {
300 6
        $class = get_class($object);
301 6
        if (isset($this->annotations[$class][$annotationClass])) {
302 4
            return $this->annotations[$class][$annotationClass];
303
        }
304
305 6
        if (!isset($this->annotations[$class])) {
306 6
            $this->annotations[$class] = [];
307
        }
308
309 6
        $this->annotations[$class][$annotationClass] = $this->recursivelyGetAnnotation($class, $annotationClass);
310 6
        return $this->annotations[$class][$annotationClass];
311
    }
312
313
    /**
314
     * @param string $class
315
     * @param string $annotationClass
316
     * @return null|AnnotationInterface
317
     */
318 6
    protected function recursivelyGetAnnotation(string $class, string $annotationClass)
319
    {
320 6
        if (!class_exists($class)) {
321
            return null;
322
        }
323
324 6
        $annotation = $this->annotationReader
325 6
            ->getClassAnnotation(
326 6
                new ReflectionClass($class),
327
                $annotationClass
328
            )
329
        ;
330
331 6
        if ($annotation !== null &&
332 6
            get_class($annotation) === $annotationClass
333
        ) {
334 6
            return $annotation;
335
        }
336
337 6
        $parentClass = get_parent_class($class);
338
339 6
        if ($parentClass === false || !class_exists($parentClass)) {
340 5
            return null;
341
        }
342
343 6
        return $this->recursivelyGetAnnotation($parentClass, $annotationClass);
344
    }
345
346
    /**
347
     * @param string $resource
348
     * @param ResourceInterface $object
349
     * @return ResourceInterface
350
     */
351 1
    public function buildAsyncFromSync(string $resource, ResourceInterface $object): ResourceInterface
352
    {
353 1
        return $this->hydrateFQCN(
354
            $this->options[Options::NAMESPACE] . '\\Async\\' . $resource,
355 1
            $this->extractFQCN(
356
                $this->options[Options::NAMESPACE] . '\\Sync\\' . $resource,
357
                $object
358
            )
359
        );
360
    }
361
362
    /**
363
     * @param string $class
364
     * @return HydratorInterface
365
     */
366 8
    protected function getHydrator(string $class): HydratorInterface
367
    {
368 8
        if (isset($this->hydrators[$class])) {
369 6
            return $this->hydrators[$class];
370
        }
371
372 8
        $config = new Configuration($class);
373 8
        if (isset($this->options[Options::RESOURCE_CACHE_DIR])) {
374 8
            $config->setGeneratedClassesTargetDir($this->options[Options::RESOURCE_CACHE_DIR]);
375
        }
376 8
        if (isset($this->options[Options::RESOURCE_NAMESPACE])) {
377 8
            $config->setGeneratedClassesNamespace($this->options[Options::RESOURCE_NAMESPACE]);
378
        }
379 8
        $hydrator = $config->createFactory()->getHydratorClass();
380 8
        $this->hydrators[$class] = new $hydrator;
381
382 8
        return $this->hydrators[$class];
383
    }
384
}
385