Passed
Push — 2.2 ( 5b074d...ea4446 )
by Kévin
08:55
created

Resolver/Factory/ItemMutationResolverFactory.php (2 issues)

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
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 ignore-type  annotation

99
                    $context += $resourceMetadata->getGraphqlAttribute(/** @scrutinizer ignore-type */ $operationName, 'denormalization_context', [], true);
Loading history...
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 ignore-type  annotation

129
        $validationGroups = $resourceMetadata->getGraphqlAttribute(/** @scrutinizer ignore-type */ $operationName, 'validation_groups', null, true);
Loading history...
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