1 | <?php |
||||
2 | |||||
3 | /* |
||||
4 | * This file is part of the API Platform project. |
||||
5 | * |
||||
6 | * (c) Kévin Dunglas <[email protected]> |
||||
7 | * |
||||
8 | * For the full copyright and license information, please view the LICENSE |
||||
9 | * file that was distributed with this source code. |
||||
10 | */ |
||||
11 | |||||
12 | declare(strict_types=1); |
||||
13 | |||||
14 | namespace ApiPlatform\Core\GraphQl\Resolver\Factory; |
||||
15 | |||||
16 | use ApiPlatform\Core\Api\IriConverterInterface; |
||||
17 | use ApiPlatform\Core\DataPersister\DataPersisterInterface; |
||||
18 | use ApiPlatform\Core\Exception\InvalidArgumentException; |
||||
19 | use ApiPlatform\Core\Exception\ItemNotFoundException; |
||||
20 | use ApiPlatform\Core\GraphQl\Resolver\ResourceAccessCheckerTrait; |
||||
21 | use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; |
||||
22 | use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
||||
23 | use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; |
||||
24 | use ApiPlatform\Core\Security\ResourceAccessCheckerInterface; |
||||
25 | use ApiPlatform\Core\Util\ClassInfoTrait; |
||||
26 | use ApiPlatform\Core\Validator\Exception\ValidationException; |
||||
27 | use ApiPlatform\Core\Validator\ValidatorInterface; |
||||
28 | use GraphQL\Error\Error; |
||||
29 | use GraphQL\Type\Definition\ResolveInfo; |
||||
30 | use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; |
||||
31 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
||||
32 | |||||
33 | /** |
||||
34 | * Creates a function resolving a GraphQL mutation of an item. |
||||
35 | * |
||||
36 | * @experimental |
||||
37 | * |
||||
38 | * @author Alan Poulain <[email protected]> |
||||
39 | */ |
||||
40 | final class ItemMutationResolverFactory implements ResolverFactoryInterface |
||||
41 | { |
||||
42 | use ClassInfoTrait; |
||||
43 | use ResourceAccessCheckerTrait; |
||||
44 | |||||
45 | private $iriConverter; |
||||
46 | private $dataPersister; |
||||
47 | private $normalizer; |
||||
48 | private $resourceMetadataFactory; |
||||
49 | private $resourceAccessChecker; |
||||
50 | private $validator; |
||||
51 | |||||
52 | public function __construct(IriConverterInterface $iriConverter, DataPersisterInterface $dataPersister, NormalizerInterface $normalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourceAccessCheckerInterface $resourceAccessChecker = null, ValidatorInterface $validator = null) |
||||
53 | { |
||||
54 | if (!$normalizer instanceof DenormalizerInterface) { |
||||
55 | throw new InvalidArgumentException(sprintf('The normalizer must implements the "%s" interface', DenormalizerInterface::class)); |
||||
56 | } |
||||
57 | |||||
58 | $this->iriConverter = $iriConverter; |
||||
59 | $this->dataPersister = $dataPersister; |
||||
60 | $this->normalizer = $normalizer; |
||||
61 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||||
62 | $this->resourceAccessChecker = $resourceAccessChecker; |
||||
63 | $this->validator = $validator; |
||||
64 | } |
||||
65 | |||||
66 | public function __invoke(string $resourceClass = null, string $rootClass = null, string $operationName = null): callable |
||||
67 | { |
||||
68 | return function ($root, $args, $context, ResolveInfo $info) use ($resourceClass, $operationName) { |
||||
69 | if (null === $resourceClass) { |
||||
70 | return null; |
||||
71 | } |
||||
72 | |||||
73 | $data = ['clientMutationId' => $args['input']['clientMutationId'] ?? null]; |
||||
74 | $item = null; |
||||
75 | |||||
76 | $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
||||
77 | $normalizationContext = $resourceMetadata->getGraphqlAttribute($operationName ?? '', 'normalization_context', [], true); |
||||
78 | $normalizationContext['attributes'] = $info->getFieldSelection(PHP_INT_MAX); |
||||
79 | |||||
80 | if (isset($args['input']['id'])) { |
||||
81 | try { |
||||
82 | $item = $this->iriConverter->getItemFromIri($args['input']['id'], $normalizationContext); |
||||
83 | } catch (ItemNotFoundException $e) { |
||||
84 | throw Error::createLocatedError(sprintf('Item "%s" not found.', $args['input']['id']), $info->fieldNodes, $info->path); |
||||
85 | } |
||||
86 | |||||
87 | if ($resourceClass !== $this->getObjectClass($item)) { |
||||
88 | throw Error::createLocatedError(sprintf('Item "%s" did not match expected type "%s".', $args['input']['id'], $resourceClass), $info->fieldNodes, $info->path); |
||||
89 | } |
||||
90 | } |
||||
91 | |||||
92 | $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
||||
93 | $this->canAccess($this->resourceAccessChecker, $resourceMetadata, $resourceClass, $info, $item, $operationName); |
||||
94 | |||||
95 | switch ($operationName) { |
||||
96 | case 'create': |
||||
97 | case 'update': |
||||
98 | $context = null === $item ? ['resource_class' => $resourceClass] : ['resource_class' => $resourceClass, 'object_to_populate' => $item]; |
||||
99 | $context += $resourceMetadata->getGraphqlAttribute($operationName, 'denormalization_context', [], true); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
100 | $item = $this->normalizer->denormalize($args['input'], $resourceClass, ItemNormalizer::FORMAT, $context); |
||||
101 | $this->validate($item, $info, $resourceMetadata, $operationName); |
||||
102 | $this->dataPersister->persist($item); |
||||
103 | |||||
104 | return $this->normalizer->normalize($item, ItemNormalizer::FORMAT, $normalizationContext) + $data; |
||||
105 | case 'delete': |
||||
106 | if ($item) { |
||||
107 | $this->dataPersister->remove($item); |
||||
108 | $data['id'] = $args['input']['id']; |
||||
109 | } else { |
||||
110 | $data['id'] = null; |
||||
111 | } |
||||
112 | } |
||||
113 | |||||
114 | return $data; |
||||
115 | }; |
||||
116 | } |
||||
117 | |||||
118 | /** |
||||
119 | * @param object $item |
||||
120 | * |
||||
121 | * @throws Error |
||||
122 | */ |
||||
123 | private function validate($item, ResolveInfo $info, ResourceMetadata $resourceMetadata, string $operationName = null) |
||||
124 | { |
||||
125 | if (null === $this->validator) { |
||||
126 | return; |
||||
127 | } |
||||
128 | |||||
129 | $validationGroups = $resourceMetadata->getGraphqlAttribute($operationName, 'validation_groups', null, true); |
||||
0 ignored issues
–
show
It seems like
$operationName can also be of type null ; however, parameter $operationName of ApiPlatform\Core\Metadat...::getGraphqlAttribute() 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
![]() |
|||||
130 | try { |
||||
131 | $this->validator->validate($item, ['groups' => $validationGroups]); |
||||
132 | } catch (ValidationException $e) { |
||||
133 | throw Error::createLocatedError($e->getMessage(), $info->fieldNodes, $info->path); |
||||
134 | } |
||||
135 | } |
||||
136 | } |
||||
137 |