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
02:05
created

Hydrator::hydrateFQCN()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

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