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
|
|
|
public function __construct(ClassesTypesMap $map, array $doctrineMapping = []) |
29
|
|
|
{ |
30
|
|
|
parent::__construct($map); |
31
|
|
|
$this->doctrineMapping = $doctrineMapping; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
public function getName(): string |
35
|
|
|
{ |
36
|
|
|
return 'Doctrine annotations '; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
public function supports(Reflector $reflector): bool |
40
|
|
|
{ |
41
|
|
|
return $reflector instanceof ReflectionProperty; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @param ReflectionProperty $reflector |
46
|
|
|
*/ |
47
|
|
|
public function guessType(ReflectionClass $reflectionClass, Reflector $reflector, array $filterGraphQLTypes = []): ?string |
48
|
|
|
{ |
49
|
|
|
if (!$reflector instanceof ReflectionProperty) { |
50
|
|
|
throw new TypeGuessingException('Doctrine type guesser only apply to properties'); |
51
|
|
|
} |
52
|
|
|
/** @var Column|null $columnAnnotation */ |
53
|
|
|
$columnAnnotation = $this->getAnnotation($reflector, Column::class); |
54
|
|
|
|
55
|
|
|
if (null !== $columnAnnotation) { |
56
|
|
|
$type = $this->resolveTypeFromDoctrineType($columnAnnotation->type); |
57
|
|
|
$nullable = $columnAnnotation->nullable; |
58
|
|
|
if ($type) { |
59
|
|
|
return $nullable ? $type : sprintf('%s!', $type); |
60
|
|
|
} else { |
61
|
|
|
throw new TypeGuessingException(sprintf('Unable to auto-guess GraphQL type from Doctrine type "%s"', $columnAnnotation->type)); |
62
|
|
|
} |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$associationAnnotations = [ |
66
|
|
|
OneToMany::class => true, |
67
|
|
|
OneToOne::class => false, |
68
|
|
|
ManyToMany::class => true, |
69
|
|
|
ManyToOne::class => false, |
70
|
|
|
]; |
71
|
|
|
|
72
|
|
|
foreach ($associationAnnotations as $associationClass => $isMultiple) { |
73
|
|
|
/** @var OneToMany|OneToOne|ManyToMany|ManyToOne|null $associationAnnotation */ |
74
|
|
|
$associationAnnotation = $this->getAnnotation($reflector, $associationClass); |
75
|
|
|
if (null !== $associationAnnotation) { |
76
|
|
|
$target = $this->fullyQualifiedClassName($associationAnnotation->targetEntity, $reflectionClass->getNamespaceName()); |
77
|
|
|
$type = $this->map->resolveType($target, ['type']); |
78
|
|
|
|
79
|
|
|
if ($type) { |
80
|
|
|
$isMultiple = $associationAnnotations[get_class($associationAnnotation)]; |
81
|
|
|
if ($isMultiple) { |
82
|
|
|
return sprintf('[%s]!', $type); |
83
|
|
|
} else { |
84
|
|
|
$isNullable = false; |
85
|
|
|
/** @var JoinColumn|null $joinColumn */ |
86
|
|
|
$joinColumn = $this->getAnnotation($reflector, JoinColumn::class); |
87
|
|
|
if (null !== $joinColumn) { |
88
|
|
|
$isNullable = $joinColumn->nullable; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
return sprintf('%s%s', $type, $isNullable ? '' : '!'); |
92
|
|
|
} |
93
|
|
|
} else { |
94
|
|
|
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
|
|
|
throw new TypeGuessingException(sprintf('No Doctrine ORM annotation found.')); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
private function getAnnotation(Reflector $reflector, string $annotationClass): ?MappingAnnotation |
102
|
|
|
{ |
103
|
|
|
$reader = $this->getAnnotationReader(); |
104
|
|
|
$annotations = []; |
105
|
|
|
switch (true) { |
106
|
|
|
case $reflector instanceof ReflectionClass: $annotations = $reader->getClassAnnotations($reflector); break; |
107
|
|
|
case $reflector instanceof ReflectionMethod: $annotations = $reader->getMethodAnnotations($reflector); break; |
108
|
|
|
case $reflector instanceof ReflectionProperty: $annotations = $reader->getPropertyAnnotations($reflector); break; |
109
|
|
|
} |
110
|
|
|
foreach ($annotations as $annotation) { |
111
|
|
|
if ($annotation instanceof $annotationClass) { |
112
|
|
|
/** @var MappingAnnotation $annotation */ |
113
|
|
|
return $annotation; |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
return null; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
private function getAnnotationReader(): AnnotationReader |
121
|
|
|
{ |
122
|
|
|
if (null === $this->annotationReader) { |
123
|
|
|
if (!class_exists(AnnotationReader::class) || |
124
|
|
|
!class_exists(AnnotationRegistry::class)) { |
125
|
|
|
throw new RuntimeException('In order to use graphql annotation/attributes, you need to require doctrine annotations'); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
AnnotationRegistry::registerLoader('class_exists'); |
|
|
|
|
129
|
|
|
$this->annotationReader = new AnnotationReader(); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return $this->annotationReader; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Resolve a FQN from classname and namespace. |
137
|
|
|
* |
138
|
|
|
* @internal |
139
|
|
|
*/ |
140
|
|
|
public function fullyQualifiedClassName(string $className, string $namespace): string |
141
|
|
|
{ |
142
|
|
|
if (false === strpos($className, '\\') && $namespace) { |
143
|
|
|
return $namespace.'\\'.$className; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return $className; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Resolve a GraphQLType from a doctrine type. |
151
|
|
|
*/ |
152
|
|
|
private function resolveTypeFromDoctrineType(string $doctrineType): ?string |
153
|
|
|
{ |
154
|
|
|
if (isset($this->doctrineMapping[$doctrineType])) { |
155
|
|
|
return $this->doctrineMapping[$doctrineType]; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
switch ($doctrineType) { |
159
|
|
|
case 'integer': |
160
|
|
|
case 'smallint': |
161
|
|
|
case 'bigint': |
162
|
|
|
return 'Int'; |
163
|
|
|
case 'string': |
164
|
|
|
case 'text': |
165
|
|
|
return 'String'; |
166
|
|
|
case 'bool': |
167
|
|
|
case 'boolean': |
168
|
|
|
return 'Boolean'; |
169
|
|
|
case 'float': |
170
|
|
|
case 'decimal': |
171
|
|
|
return 'Float'; |
172
|
|
|
default: |
173
|
|
|
return null; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
|
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.