Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Pull Request — master (#867)
by Vincent
23:00
created

DoctrineTypeGuesser   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Test Coverage

Coverage 97.3%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 79
c 3
b 0
f 0
dl 0
loc 153
ccs 72
cts 74
cp 0.973
rs 9.2
wmc 40

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getAnnotation() 0 17 6
A getName() 0 3 1
A supports() 0 3 1
B resolveTypeFromDoctrineType() 0 22 11
C guessType() 0 52 12
A getAnnotationReader() 0 15 5
A fullyQualifiedClassName() 0 7 3

How to fix   Complexity   

Complex Class

Complex classes like DoctrineTypeGuesser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DoctrineTypeGuesser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\AnnotationRegistry;
9
use Doctrine\ORM\Mapping\Annotation as MappingAnnotation;
10
use Doctrine\ORM\Mapping\Column;
11
use Doctrine\ORM\Mapping\JoinColumn;
12
use Doctrine\ORM\Mapping\ManyToMany;
13
use Doctrine\ORM\Mapping\ManyToOne;
14
use Doctrine\ORM\Mapping\OneToMany;
15
use Doctrine\ORM\Mapping\OneToOne;
16
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\ClassesTypesMap;
17
use ReflectionClass;
18
use ReflectionMethod;
19
use ReflectionProperty;
20
use Reflector;
21
use RuntimeException;
22
23
class DoctrineTypeGuesser extends TypeGuesser
24
{
25
    protected ?AnnotationReader $annotationReader = null;
26
    protected array $doctrineMapping = [];
27
28 93
    public function __construct(ClassesTypesMap $map, array $doctrineMapping = [])
29
    {
30 93
        parent::__construct($map);
31 93
        $this->doctrineMapping = $doctrineMapping;
32 93
    }
33
34 4
    public function getName(): string
35
    {
36 4
        return 'Doctrine annotations ';
37
    }
38
39 51
    public function supports(Reflector $reflector): bool
40
    {
41 51
        return $reflector instanceof ReflectionProperty;
42
    }
43
44
    /**
45
     * @param ReflectionProperty $reflector
46
     */
47 52
    public function guessType(ReflectionClass $reflectionClass, Reflector $reflector, array $filterGraphQLTypes = []): ?string
48
    {
49 52
        if (!$reflector instanceof ReflectionProperty) {
50 1
            throw new TypeGuessingException('Doctrine type guesser only apply to properties.');
51
        }
52
        /** @var Column|null $columnAnnotation */
53 52
        $columnAnnotation = $this->getAnnotation($reflector, Column::class);
54
55 52
        if (null !== $columnAnnotation) {
56 51
            $type = $this->resolveTypeFromDoctrineType($columnAnnotation->type ?: 'string');
57 51
            $nullable = $columnAnnotation->nullable;
58 51
            if ($type) {
59 51
                return $nullable ? $type : sprintf('%s!', $type);
60
            } else {
61 2
                throw new TypeGuessingException(sprintf('Unable to auto-guess GraphQL type from Doctrine type "%s"', $columnAnnotation->type));
62
            }
63
        }
64
65 52
        $associationAnnotations = [
66
            OneToMany::class => true,
67
            OneToOne::class => false,
68
            ManyToMany::class => true,
69
            ManyToOne::class => false,
70
        ];
71
72 52
        foreach ($associationAnnotations as $associationClass => $isMultiple) {
73
            /** @var OneToMany|OneToOne|ManyToMany|ManyToOne|null $associationAnnotation */
74 52
            $associationAnnotation = $this->getAnnotation($reflector, $associationClass);
75 52
            if (null !== $associationAnnotation) {
76 51
                $target = $this->fullyQualifiedClassName($associationAnnotation->targetEntity, $reflectionClass->getNamespaceName());
0 ignored issues
show
Bug introduced by
It seems like $associationAnnotation->targetEntity can also be of type null; however, parameter $className of Overblog\GraphQLBundle\C...llyQualifiedClassName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
                $target = $this->fullyQualifiedClassName(/** @scrutinizer ignore-type */ $associationAnnotation->targetEntity, $reflectionClass->getNamespaceName());
Loading history...
77 51
                $type = $this->map->resolveType($target, ['type']);
78
79 51
                if ($type) {
80 51
                    $isMultiple = $associationAnnotations[get_class($associationAnnotation)];
81 51
                    if ($isMultiple) {
82 51
                        return sprintf('[%s]!', $type);
83
                    } else {
84 51
                        $isNullable = false;
85
                        /** @var JoinColumn|null $joinColumn */
86 51
                        $joinColumn = $this->getAnnotation($reflector, JoinColumn::class);
87 51
                        if (null !== $joinColumn) {
88 51
                            $isNullable = $joinColumn->nullable;
89
                        }
90
91 51
                        return sprintf('%s%s', $type, $isNullable ? '' : '!');
92
                    }
93
                } else {
94 2
                    throw new TypeGuessingException(sprintf('Unable to auto-guess GraphQL type from Doctrine target class "%s" (check if the target class is a GraphQL type itself (with a @Metadata\Type metadata).', $target));
95
                }
96
            }
97
        }
98 1
        throw new TypeGuessingException(sprintf('No Doctrine ORM annotation found.'));
99
    }
100
101 52
    private function getAnnotation(Reflector $reflector, string $annotationClass): ?MappingAnnotation
102
    {
103 52
        $reader = $this->getAnnotationReader();
104 52
        $annotations = [];
105
        switch (true) {
106 52
            case $reflector instanceof ReflectionClass: $annotations = $reader->getClassAnnotations($reflector); break;
107 52
            case $reflector instanceof ReflectionMethod: $annotations = $reader->getMethodAnnotations($reflector); break;
108 52
            case $reflector instanceof ReflectionProperty: $annotations = $reader->getPropertyAnnotations($reflector); break;
109
        }
110 52
        foreach ($annotations as $annotation) {
111 51
            if ($annotation instanceof $annotationClass) {
112
                /** @var MappingAnnotation $annotation */
113 51
                return $annotation;
114
            }
115
        }
116
117 52
        return null;
118
    }
119
120 52
    private function getAnnotationReader(): AnnotationReader
121
    {
122 52
        if (null === $this->annotationReader) {
123 52
            if (!class_exists(AnnotationReader::class) ||
124 52
                !class_exists(AnnotationRegistry::class)) {
125
                throw new RuntimeException('In order to use graphql annotation/attributes, you need to require doctrine annotations');
126
            }
127
128 52
            if (class_exists(AnnotationRegistry::class)) {
129 52
                AnnotationRegistry::registerLoader('class_exists');
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Common\Annotati...istry::registerLoader() has been deprecated: This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

129
                /** @scrutinizer ignore-deprecated */ AnnotationRegistry::registerLoader('class_exists');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
130
            }
131
            $this->annotationReader = new AnnotationReader();
132 52
        }
133
134
        return $this->annotationReader;
135
    }
136
137
    /**
138
     * Resolve a FQN from classname and namespace.
139
     *
140 51
     * @internal
141
     */
142 51
    public function fullyQualifiedClassName(string $className, string $namespace): string
143 51
    {
144
        if (false === strpos($className, '\\') && $namespace) {
145
            return $namespace.'\\'.$className;
146
        }
147
148
        return $className;
149
    }
150
151
    /**
152 51
     * Resolve a GraphQLType from a doctrine type.
153
     */
154 51
    private function resolveTypeFromDoctrineType(string $doctrineType): ?string
155 51
    {
156
        if (isset($this->doctrineMapping[$doctrineType])) {
157
            return $this->doctrineMapping[$doctrineType];
158
        }
159 51
160 51
        switch ($doctrineType) {
161 51
            case 'integer':
162 51
            case 'smallint':
163 51
            case 'bigint':
164 51
                return 'Int';
165 51
            case 'string':
166 51
            case 'text':
167 51
                return 'String';
168 51
            case 'bool':
169 51
            case 'boolean':
170 51
                return 'Boolean';
171 51
            case 'float':
172
            case 'decimal':
173 2
                return 'Float';
174
            default:
175
                return null;
176
        }
177
    }
178
}
179