• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 7930644932

16 Feb 2024 12:36PM UTC coverage: 66.683% (+0.005%) from 66.678%
7930644932

push

github

web-flow
fix: return null instead of exception for GraphQL Query operation (#6118)

0 of 2 new or added lines in 1 file covered. (0.0%)

53 existing lines in 19 files now uncovered.

16304 of 24450 relevant lines covered (66.68%)

37.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.15
/src/Symfony/EventListener/DeserializeListener.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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\Symfony\EventListener;
15

16
use ApiPlatform\Api\FormatMatcher;
17
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
18
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
19
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
20
use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
21
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpKernel\Event\RequestEvent;
24
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
25
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
26
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
27
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
28
use Symfony\Component\Serializer\SerializerInterface;
29
use Symfony\Component\Validator\Constraints\Type;
30
use Symfony\Component\Validator\ConstraintViolation;
31
use Symfony\Component\Validator\ConstraintViolationList;
32
use Symfony\Contracts\Translation\LocaleAwareInterface;
33
use Symfony\Contracts\Translation\TranslatorInterface;
34
use Symfony\Contracts\Translation\TranslatorTrait;
35

36
/**
37
 * Updates the entity retrieved by the data provider with data contained in the request body.
38
 *
39
 * @author Kévin Dunglas <dunglas@gmail.com>
40
 */
41
final class DeserializeListener
42
{
43
    use OperationRequestInitiatorTrait;
44

45
    public const OPERATION_ATTRIBUTE_KEY = 'deserialize';
46

47
    public function __construct(private readonly SerializerInterface $serializer, private readonly SerializerContextBuilderInterface $serializerContextBuilder, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, private ?TranslatorInterface $translator = null)
48
    {
49
        $this->resourceMetadataCollectionFactory = $resourceMetadataFactory;
164✔
50
        if (null === $this->translator) {
164✔
51
            $this->translator = new class() implements TranslatorInterface, LocaleAwareInterface {
48✔
52
                use TranslatorTrait;
53
            };
48✔
54
            $this->translator->setLocale('en');
48✔
55
        }
56
    }
57

58
    /**
59
     * Deserializes the data sent in the requested format.
60
     *
61
     * @throws UnsupportedMediaTypeHttpException
62
     */
63
    public function onKernelRequest(RequestEvent $event): void
64
    {
65
        $request = $event->getRequest();
164✔
66
        $method = $request->getMethod();
164✔
67

68
        if (
69
            'DELETE' === $method
164✔
70
            || $request->isMethodSafe()
164✔
71
            || !($attributes = RequestAttributesExtractor::extractAttributes($request))
164✔
72
            || !$attributes['receive']
164✔
73
            || $request->attributes->get('_api_platform_disable_listeners')
164✔
74
        ) {
75
            return;
112✔
76
        }
77

78
        $operation = $this->initializeOperation($request);
52✔
79

80
        if ('api_platform.symfony.main_controller' === $operation?->getController()) {
52✔
UNCOV
81
            return;
9✔
82
        }
83

84
        if (!($operation?->canDeserialize() ?? true)) {
43✔
85
            return;
4✔
86
        }
87

88
        $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes);
39✔
89

90
        $format = $this->getFormat($request, $operation?->getInputFormats() ?? []);
39✔
91
        $data = $request->attributes->get('data');
27✔
92
        if (
93
            null !== $data
27✔
94
            && (
95
                'POST' === $method
27✔
96
                || 'PATCH' === $method
27✔
97
                || ('PUT' === $method && !($operation->getExtraProperties()['standard_put'] ?? false))
27✔
98
            )
99
        ) {
100
            $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $data;
8✔
101
        }
102
        try {
103
            $request->attributes->set(
27✔
104
                'data',
27✔
105
                $this->serializer->deserialize($request->getContent(), $context['resource_class'], $format, $context)
27✔
106
            );
27✔
107
        } catch (PartialDenormalizationException $e) {
4✔
108
            $violations = new ConstraintViolationList();
4✔
109
            foreach ($e->getErrors() as $exception) {
4✔
110
                if (!$exception instanceof NotNormalizableValueException) {
4✔
111
                    continue;
×
112
                }
113
                $message = (new Type($exception->getExpectedTypes() ?? []))->message;
4✔
114
                $parameters = [];
4✔
115
                if ($exception->canUseMessageForUser()) {
4✔
116
                    $parameters['hint'] = $exception->getMessage();
4✔
117
                }
118
                $violations->add(new ConstraintViolation($this->translator->trans($message, ['{{ type }}' => implode('|', $exception->getExpectedTypes() ?? [])], 'validators'), $message, $parameters, null, $exception->getPath(), null, null, Type::INVALID_TYPE_ERROR));
4✔
119
            }
120
            if (0 !== \count($violations)) {
4✔
121
                throw new ValidationException($violations);
4✔
122
            }
123
        }
124
    }
125

126
    /**
127
     * Extracts the format from the Content-Type header and check that it is supported.
128
     *
129
     * @throws UnsupportedMediaTypeHttpException
130
     */
131
    private function getFormat(Request $request, array $formats): string
132
    {
133
        /** @var ?string $contentType */
134
        $contentType = $request->headers->get('CONTENT_TYPE');
39✔
135
        if (null === $contentType || '' === $contentType) {
39✔
136
            throw new UnsupportedMediaTypeHttpException('The "Content-Type" header must exist.');
8✔
137
        }
138

139
        $formatMatcher = new FormatMatcher($formats);
31✔
140
        $format = $formatMatcher->getFormat($contentType);
31✔
141
        if (null === $format) {
31✔
142
            $supportedMimeTypes = [];
4✔
143
            foreach ($formats as $mimeTypes) {
4✔
144
                foreach ($mimeTypes as $mimeType) {
4✔
145
                    $supportedMimeTypes[] = $mimeType;
4✔
146
                }
147
            }
148

149
            throw new UnsupportedMediaTypeHttpException(sprintf('The content-type "%s" is not supported. Supported MIME types are "%s".', $contentType, implode('", "', $supportedMimeTypes)));
4✔
150
        }
151

152
        return $format;
27✔
153
    }
154
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc