We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
Total Complexity | 48 |
Total Lines | 371 |
Duplicated Lines | 0 % |
Changes | 2 | ||
Bugs | 0 | Features | 0 |
Complex classes like Hydrator 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 Hydrator, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
31 | class Hydrator |
||
32 | { |
||
33 | private const ENTITY_ANNOTATIONS = [ |
||
34 | ORM\OneToOne::class, |
||
35 | ORM\OneToMany::class, |
||
36 | ORM\ManyToOne::class, |
||
37 | ORM\ManyToMany::class |
||
38 | ]; |
||
39 | |||
40 | private static array $annotationCache = []; |
||
41 | |||
42 | private AnnotationReader $annotationReader; |
||
43 | private PropertyAccessorInterface $propertyAccessor; |
||
44 | private EntityManagerInterface $em; |
||
45 | private ServiceLocator $converters; |
||
46 | private array $args; |
||
47 | |||
48 | public function __construct( |
||
49 | PropertyAccessorInterface $propertyAccessor, |
||
50 | ServiceLocator $converters, |
||
51 | EntityManagerInterface $entityManager |
||
52 | ) { |
||
53 | if (!class_exists(AnnotationReader::class) || !class_exists(AnnotationRegistry::class)) { |
||
54 | throw new RuntimeException('In order to use graphql annotation, you need to require doctrine annotations'); |
||
55 | } |
||
56 | |||
57 | AnnotationRegistry::registerLoader('class_exists'); |
||
|
|||
58 | |||
59 | $this->annotationReader = new AnnotationReader(); |
||
60 | $this->propertyAccessor = $propertyAccessor; |
||
61 | $this->converters = $converters; |
||
62 | $this->em = $entityManager; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * @throws ORM\MappingException|NonUniqueResultException|ReflectionException|Exception |
||
67 | */ |
||
68 | public function hydrate(ArgumentInterface $args, ResolveInfo $info): Models |
||
69 | { |
||
70 | $this->args = $args->getArrayCopy(); |
||
71 | $requestedField = $info->parentType->getField($info->fieldName); |
||
72 | |||
73 | $models = new Models(); |
||
74 | |||
75 | foreach ($this->args as $argName => $input) { |
||
76 | /** @var ListOfType|NonNull $argType */ |
||
77 | $argType = $requestedField->getArg($argName)->getType(); |
||
78 | |||
79 | /** @var InputObjectType $inputType */ |
||
80 | $inputType = $argType->getOfType(); |
||
81 | |||
82 | if (!isset($inputType->config['model'])) { |
||
83 | continue; |
||
84 | } |
||
85 | |||
86 | $models->models[$argName] = $this->hydrateInputType($inputType, $input); |
||
87 | } |
||
88 | |||
89 | return $models; |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * @param mixed $inputValues |
||
94 | * |
||
95 | * @return object |
||
96 | * |
||
97 | * @throws ORM\MappingException|ReflectionException|NonUniqueResultException |
||
98 | */ |
||
99 | private function hydrateInputType(InputObjectType $inputType, $inputValues, object $model = null): object |
||
100 | { |
||
101 | if (empty($inputType->config['model'])) { |
||
102 | return $inputValues; |
||
103 | } |
||
104 | |||
105 | $modelName = null !== $model ? get_class($model) : $inputType->config['model']; |
||
106 | $modelReflection = new ReflectionClass($modelName); |
||
107 | |||
108 | $entityAnnotation = $this->annotationReader->getClassAnnotation($modelReflection, Orm\Entity::class); |
||
109 | |||
110 | if (null === $model) { |
||
111 | if (null !== $entityAnnotation) { |
||
112 | $model = $this->getEntityModel($modelName, $inputValues); |
||
113 | } else { |
||
114 | $model = new $modelName(); |
||
115 | } |
||
116 | } |
||
117 | |||
118 | $annotationMapping = $this->readAnnotationMapping($modelReflection); |
||
119 | $fields = $inputType->getFields(); |
||
120 | |||
121 | foreach ($inputValues as $fieldName => $fieldValue) { |
||
122 | if (empty($fields[$fieldName])) { |
||
123 | continue; |
||
124 | } |
||
125 | |||
126 | $fieldObject = $fields[$fieldName]; |
||
127 | $targetField = $annotationMapping[$fieldName] ?? $fieldName; |
||
128 | |||
129 | if ($this->propertyAccessor->isWritable($model, $targetField)) { |
||
130 | |||
131 | $resultValue = $this->resolveConverter( |
||
132 | $modelName, |
||
133 | $targetField, |
||
134 | $fieldValue, |
||
135 | $modelReflection, |
||
136 | $fieldObject |
||
137 | ); |
||
138 | |||
139 | $this->propertyAccessor->setValue( |
||
140 | $model, |
||
141 | $targetField, |
||
142 | $resultValue |
||
143 | ); |
||
144 | } |
||
145 | } |
||
146 | |||
147 | return $model; |
||
148 | } |
||
149 | |||
150 | public function resolveConverter($modelName, $targetField, $fieldValue, $modelReflection, $fieldObject) |
||
151 | { |
||
152 | # 1. Check if converter declared explicitely ----------------------------------------------------------- |
||
153 | $converterAnnotation = $this->getPropertyAnnotation($modelName, $targetField, ConverterAnnotationInterface::class); |
||
154 | if (null !== $converterAnnotation) { |
||
155 | $converter = $this->converters->get($converterAnnotation::getConverterClass()); |
||
156 | $resultValue = $converter->convert($fieldValue, $converterAnnotation); |
||
157 | } |
||
158 | |||
159 | # ------------------------------------------------------------------------------------------------------ |
||
160 | |||
161 | # 2. Check if an entity converter can be applied automatically (single or collection) |
||
162 | // Check if target property has a type-hint which is en Entity itself |
||
163 | $typeHint = $modelReflection->getProperty($targetField)->getType(); |
||
164 | if (null !== $typeHint && class_exists((string) $typeHint)) { |
||
165 | $typeAnno = $this->annotationReader->getClassAnnotation(new ReflectionClass($typeHint), ORM\Entity::class); |
||
166 | if (null !== $typeAnno) { |
||
167 | // use entity converter |
||
168 | $converter = $this->converters->get(Converters\Entity::class); |
||
169 | $a = new Converters\Entity; |
||
170 | $a->value = (string) $typeHint; |
||
171 | $resultValue = $converter->convert($fieldValue, $a); |
||
172 | } |
||
173 | } |
||
174 | |||
175 | // Check if target property has a Doctrine annotation declared on it |
||
176 | foreach (self::ENTITY_ANNOTATIONS as $annotationName) { |
||
177 | /** @var ORM\OneToOne|ORM\OneToMany|ORM\ManyToOne|ORM\ManyToMany $a */ |
||
178 | $columnAnnotation = $this->getPropertyAnnotation($modelName, $targetField, $annotationName); |
||
179 | |||
180 | if (null !== $columnAnnotation) { |
||
181 | if (strpos($columnAnnotation->targetEntity, '\\') === false) { |
||
182 | // Fix namespace |
||
183 | $columnAnnotation->targetEntity = $modelReflection->getNamespaceName()."\\$columnAnnotation->targetEntity"; |
||
184 | } |
||
185 | |||
186 | switch (true) { |
||
187 | case $columnAnnotation instanceof ORM\OneToOne: |
||
188 | case $columnAnnotation instanceof ORM\ManyToOne: |
||
189 | $converter = $this->converters->get($columnAnnotation->targetEntity); |
||
190 | $entity = new Entity(); |
||
191 | $entity->value = ""; |
||
192 | $entity->isCollection = true; |
||
193 | $resultValue = $converter->convert($fieldValue, $entity); |
||
194 | break; |
||
195 | |||
196 | case $columnAnnotation instanceof ORM\OneToMany: |
||
197 | case $columnAnnotation instanceof ORM\ManyToMany: |
||
198 | $converter = $this->converters->get($columnAnnotation->targetEntity); |
||
199 | $entity = new Entity(); |
||
200 | $entity->value = ""; |
||
201 | $entity->isCollection = true; |
||
202 | $resultValue = $converter->convert($fieldValue, $columnAnnotation); |
||
203 | break; |
||
204 | } |
||
205 | |||
206 | break; |
||
207 | } |
||
208 | } |
||
209 | |||
210 | # ------------------------------------------------------------------------------------------------------ |
||
211 | |||
212 | # 3. Use default converter (single or collection) |
||
213 | if (Type::getNullableType($fieldObject->getType()) instanceof ListOfType) { |
||
214 | $resultValue = $this->hydrateCollectionValue($fieldObject, $fieldValue, $modelName); |
||
215 | } else { |
||
216 | $resultValue = $this->hydrateValue($fieldObject, $fieldValue, $resultValue); |
||
217 | } |
||
218 | |||
219 | return $resultValue; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Returns property annotation from cache. |
||
224 | * |
||
225 | * @throws ReflectionException |
||
226 | */ |
||
227 | private function getPropertyAnnotation(string $className, string $propertyName, string $annotationName): ?object |
||
228 | { |
||
229 | self::$annotationCache[$className][$propertyName] ??= $this->annotationReader->getPropertyAnnotations(new ReflectionProperty($className, $propertyName)); |
||
230 | |||
231 | foreach (self::$annotationCache[$className][$propertyName] as $annotation) { |
||
232 | if ($annotation instanceof $annotationName) { |
||
233 | return $annotation; |
||
234 | } |
||
235 | } |
||
236 | |||
237 | return null; |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * Returns class annotation from cache. |
||
242 | * |
||
243 | * @throws ReflectionException |
||
244 | */ |
||
245 | private function getClassAnnotation(string $className, string $annotationName): ?object |
||
246 | { |
||
247 | static $cache = []; |
||
248 | |||
249 | return $cache[$className][$annotationName] ??= |
||
250 | $this->annotationReader->getClassAnnotation(new ReflectionClass($className), $annotationName); |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * @param string $modelName |
||
255 | * @param array $inputValues |
||
256 | * @return int|mixed|string|null |
||
257 | * |
||
258 | * @throws ORM\MappingException|NonUniqueResultException|ReflectionException |
||
259 | */ |
||
260 | private function getEntityModel(string $modelName, array $inputValues) |
||
261 | { |
||
262 | $idValue = $this->resolveIdValue($modelName, $inputValues); |
||
263 | |||
264 | if (null === $idValue) { |
||
265 | return new $modelName(); |
||
266 | } |
||
267 | |||
268 | // entity |
||
269 | $meta = $this->em->getClassMetadata($modelName); |
||
270 | $entityIdField = $meta->getSingleIdentifierFieldName(); |
||
271 | |||
272 | $builder = $this->em->createQueryBuilder() |
||
273 | ->select('o') |
||
274 | ->from($modelName, 'o') |
||
275 | ->where("o.$entityIdField = :identifier") |
||
276 | ->setParameter('identifier', $idValue); |
||
277 | |||
278 | $result = $builder->getQuery()->getOneOrNullResult(); |
||
279 | |||
280 | if (null === $result) { |
||
281 | throw new Exception("Couldn't find entity"); |
||
282 | } |
||
283 | |||
284 | return $result; |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * @return array|mixed|null |
||
289 | * @throws ReflectionException |
||
290 | */ |
||
291 | private function resolveIdValue(string $modelName, array $inputValues) |
||
292 | { |
||
293 | $reflectionClass = new ReflectionClass($modelName); |
||
294 | $modelAnnotation = $this->annotationReader->getClassAnnotation($reflectionClass, Model::class); |
||
295 | |||
296 | $identifier = $modelAnnotation->identifier ?? 'id'; |
||
297 | $path = explode('.', $identifier); |
||
298 | |||
299 | // If a path is provided, search the value from top argument down |
||
300 | if (count($path) > 1) { |
||
301 | $temp = &$this->args; |
||
302 | foreach($path as $key) { |
||
303 | $temp = &$temp[$key]; |
||
304 | } |
||
305 | return $temp; |
||
306 | } |
||
307 | |||
308 | return $inputValues[$identifier] ?? $this->args[$identifier] ?? null; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * @param $fieldObject |
||
313 | * @param $fieldValue |
||
314 | * @return mixed |
||
315 | * @throws ReflectionException |
||
316 | */ |
||
317 | private function hydrateValue($fieldObject, $fieldValue, object $model = null) |
||
318 | { |
||
319 | $field = Type::getNamedType($fieldObject->getType()); |
||
320 | |||
321 | if ($field instanceof InputObjectType) { |
||
322 | $fieldValue = $this->hydrateInputType($field, $fieldValue, $model); |
||
323 | } |
||
324 | |||
325 | return $fieldValue; |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * @param $fieldObject |
||
330 | * @param $fieldValue |
||
331 | * @param string $modelName |
||
332 | * @return array |
||
333 | * @throws ORM\MappingException |
||
334 | * @throws ReflectionException |
||
335 | */ |
||
336 | private function hydrateCollectionValue($fieldObject, $fieldValue, string $modelName) |
||
337 | { |
||
338 | $isBuiltInTypes = Type::isBuiltInType(Type::getNamedType($fieldObject->getType())); |
||
339 | |||
340 | if (true === $isBuiltInTypes) { |
||
341 | $meta = $this->em->getClassMetadata($modelName); |
||
342 | $entityIdField = $meta->getSingleIdentifierFieldName(); |
||
343 | |||
344 | $query = $this->em->createQuery(<<<DQL |
||
345 | SELECT o FROM $modelName o |
||
346 | WHERE o.$entityIdField IN (:ids) |
||
347 | INDEX BY o.$entityIdField |
||
348 | DQL); |
||
349 | |||
350 | $query->setParameter('ids', $fieldValue); |
||
351 | $entities = $query->getResult(); |
||
352 | |||
353 | if (count($entities) !== count($fieldValue)) { |
||
354 | throw new Exception("Couldn't find all entities."); |
||
355 | } |
||
356 | } |
||
357 | |||
358 | $result = []; |
||
359 | foreach ($fieldValue as $value) { |
||
360 | $result[] = $this->hydrateValue($fieldObject, $value, $entities[$value] ?? null); |
||
361 | } |
||
362 | |||
363 | return $result; |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * @param mixed $value |
||
368 | * @return mixed |
||
369 | * @throws ReflectionException |
||
370 | */ |
||
371 | private function convertValue($value, object $model, string $targetName) |
||
385 | } |
||
386 | |||
387 | private function readAnnotationMapping(ReflectionClass $reflectionClass): array |
||
388 | { |
||
389 | $reader = AnnotationParser::getAnnotationReader(); |
||
390 | $properties = $reflectionClass->getProperties(); |
||
391 | |||
392 | $mapping = []; |
||
393 | foreach ($properties as $property) { |
||
394 | $annotation = $reader->getPropertyAnnotation($property, Field::class); |
||
402 | } |
||
403 | } |
||
404 |
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.