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 (#14)
by Cees-Jan
03:48
created

Hydrator::hydrate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 12
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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