Completed
Pull Request — master (#13)
by Rafael
06:46
created

AnnotationLoader   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 123
Duplicated Lines 0 %

Test Coverage

Coverage 48.94%

Importance

Changes 0
Metric Value
wmc 20
eloc 52
dl 0
loc 123
ccs 23
cts 47
cp 0.4894
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A extractNamespaceClasses() 0 18 5
A __construct() 0 5 1
B loadDefinitions() 0 19 8
A resolveClasses() 0 23 6
1
<?php
2
/*******************************************************************************
3
 *  This file is part of the GraphQL Bundle package.
4
 *
5
 *  (c) YnloUltratech <[email protected]>
6
 *
7
 *  For the full copyright and license information, please view the LICENSE
8
 *  file that was distributed with this source code.
9
 ******************************************************************************/
10
11
namespace Ynlo\GraphQLBundle\Definition\Loader;
12
13
use Doctrine\Common\Annotations\Reader;
14
use Symfony\Component\Finder\Finder;
15
use Symfony\Component\HttpKernel\Kernel;
16
use Symfony\Component\HttpKernel\KernelInterface;
17
use Ynlo\GraphQLBundle\Definition\Loader\Annotation\AnnotationParserInterface;
18
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
19
20
/**
21
 * Resolve and load definitions based on common annotations
22
 */
23
class AnnotationLoader implements DefinitionLoaderInterface
24
{
25
    /**
26
     * Folders inside bundles to locate definitions
27
     * TODO: allow add additional mapping on bundle config
28
     */
29
    private const DEFINITIONS_LOCATIONS = [
30
        'Model', //non persistent models like interfaces, or abstract classes
31
        'Entity', //doctrine entities
32
        'Mutation', //custom actions
33
        'Subscription', //custom subscriptions
34
        'Query', //custom actions
35
    ];
36
37
    /**
38
     * @var Kernel
39
     */
40
    protected $kernel;
41
42
    /**
43
     * @var Reader
44
     */
45
    protected $reader;
46
47
    /**
48
     * @var iterable|array|AnnotationParserInterface[]
49
     */
50
    protected $annotationParsers = [];
51
52
    /**
53
     * @param KernelInterface                      $kernel
54
     * @param Reader                               $reader
55
     * @param iterable|AnnotationParserInterface[] $annotationParsers
56
     */
57 1
    public function __construct(KernelInterface $kernel, Reader $reader, $annotationParsers = [])
58
    {
59 1
        $this->kernel = $kernel;
0 ignored issues
show
Documentation Bug introduced by
$kernel is of type Symfony\Component\HttpKernel\KernelInterface, but the property $kernel was declared to be of type Symfony\Component\HttpKernel\Kernel. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
60 1
        $this->reader = $reader;
61 1
        $this->annotationParsers = $annotationParsers;
62 1
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 1
    public function loadDefinitions(Endpoint $endpoint): void
68
    {
69 1
        $classesToLoad = $this->resolveClasses();
70 1
        $annotationsMapping = [];
71 1
        foreach ($classesToLoad as $class) {
72
            $refClass = new \ReflectionClass($class);
73
            $annotations = $this->reader->getClassAnnotations($refClass);
74
            if ($annotations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $annotations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
75
                $annotationsMapping[$refClass->getName()] = [$refClass, $annotations];
76
            }
77
        }
78
79 1
        foreach ($this->annotationParsers as $parser) {
80 1
            if ($parser instanceof AnnotationParserInterface) {
81 1
                foreach ($annotationsMapping as $className => $map) {
82
                    list($refClass, $annotations) = $map;
83
                    foreach ($annotations as $annotation) {
84
                        if ($parser->supports($annotation)) {
85
                            $parser->parse($annotation, $refClass, $endpoint);
86
                        }
87
                    }
88
                }
89
            }
90
        }
91 1
    }
92
93
    /**
94
     * @return array
95
     */
96 1
    protected function resolveClasses(): array
97
    {
98 1
        $bundles = $this->kernel->getBundles();
99 1
        $classes = [[]];
100 1
        foreach (self::DEFINITIONS_LOCATIONS as $definitionLocation) {
101 1
            foreach ($bundles as $bundle) {
102 1
                $path = $bundle->getPath().'/'.$definitionLocation;
103 1
                if (file_exists($path)) {
104
                    $classes[] = $this->extractNamespaceClasses($path, $bundle->getNamespace(), $definitionLocation);
105
                }
106
            }
107
108 1
            if (Kernel::VERSION_ID >= 40000) {
109
                $path = $this->kernel->getRootDir().'/'.$definitionLocation;
110
                if (file_exists($path)) {
111
                    $classes[] = $this->extractNamespaceClasses($path, 'App', $definitionLocation);
112
                }
113
            }
114
        }
115
116 1
        $classes = array_merge(...$classes);
117
118 1
        return array_unique($classes);
119
    }
120
121
    /**
122
     * @param string $path
123
     * @param string $baseNamespace
124
     * @param string $baseLocation
125
     *
126
     * @return array
127
     */
128
    protected function extractNamespaceClasses($path, $baseNamespace, $baseLocation)
129
    {
130
        $classes = [];
131
        $finder = new Finder();
132
        foreach ($finder->in($path)->name('/.php$/')->getIterator() as $file) {
133
            $className = preg_replace('/.php$/', null, $file->getFilename());
134
            if ($file->getRelativePath()) {
135
                $subNamespace = str_replace('/', '\\', $file->getRelativePath());
136
                $fullyClassName = $baseNamespace.'\\'.$baseLocation.'\\'.$subNamespace.'\\'.$className;
137
            } else {
138
                $fullyClassName = $baseNamespace.'\\'.$baseLocation.'\\'.$className;
139
            }
140
            if (class_exists($fullyClassName) || interface_exists($fullyClassName)) {
141
                $classes[] = $fullyClassName;
142
            }
143
        }
144
145
        return $classes;
146
    }
147
}
148