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

api-platform / core / 8100440090

29 Feb 2024 05:54PM UTC coverage: 57.09% (+0.2%) from 56.922%
8100440090

Pull #6138

github

web-flow
chore(deps): bump codecov/codecov-action from 3 to 4

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #6138: chore(deps): bump codecov/codecov-action from 3 to 4

9538 of 16707 relevant lines covered (57.09%)

30.81 hits per line

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

81.08
/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\HttpOperation;
18
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
19
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
20
use ApiPlatform\State\ProviderInterface;
21
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
22
use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
23
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpKernel\Event\RequestEvent;
26
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
27
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
28
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
29
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
30
use Symfony\Component\Serializer\SerializerInterface;
31
use Symfony\Component\Validator\Constraints\Type;
32
use Symfony\Component\Validator\ConstraintViolation;
33
use Symfony\Component\Validator\ConstraintViolationList;
34
use Symfony\Contracts\Translation\LocaleAwareInterface;
35
use Symfony\Contracts\Translation\TranslatorInterface;
36
use Symfony\Contracts\Translation\TranslatorTrait;
37

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

47
    public const OPERATION_ATTRIBUTE_KEY = 'deserialize';
48
    private SerializerInterface $serializer;
49
    private ?ProviderInterface $provider = null;
50

51
    public function __construct(ProviderInterface|SerializerInterface $serializer, private readonly SerializerContextBuilderInterface|ResourceMetadataCollectionFactoryInterface|null $serializerContextBuilder = null, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, private ?TranslatorInterface $translator = null)
52
    {
53
        if ($serializer instanceof ProviderInterface) {
67✔
54
            $this->provider = $serializer;
×
55
        } else {
56
            trigger_deprecation('api-platform/core', '3.3', 'Use a "%s" as first argument in "%s" instead of "%s".', ProviderInterface::class, self::class, SerializerInterface::class);
67✔
57
            $this->serializer = $serializer;
67✔
58
        }
59

60
        if ($serializerContextBuilder instanceof ResourceMetadataCollectionFactoryInterface) {
67✔
61
            $resourceMetadataFactory = $serializerContextBuilder;
×
62
        } else {
63
            trigger_deprecation('api-platform/core', '3.3', 'Use a "%s" as second argument in "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, self::class, SerializerContextBuilderInterface::class);
67✔
64
        }
65

66
        $this->resourceMetadataCollectionFactory = $resourceMetadataFactory;
67✔
67
        if (null === $this->translator) {
67✔
68
            $this->translator = new class() implements TranslatorInterface, LocaleAwareInterface {
36✔
69
                use TranslatorTrait;
70
            };
36✔
71
            $this->translator->setLocale('en');
36✔
72
        }
73
    }
74

75
    /**
76
     * Deserializes the data sent in the requested format.
77
     *
78
     * @throws UnsupportedMediaTypeHttpException
79
     */
80
    public function onKernelRequest(RequestEvent $event): void
81
    {
82
        $request = $event->getRequest();
67✔
83
        $method = $request->getMethod();
67✔
84

85
        if (
86
            !($attributes = RequestAttributesExtractor::extractAttributes($request))
67✔
87
            || !$attributes['receive']
67✔
88
        ) {
89
            return;
22✔
90
        }
91

92
        $operation = $this->initializeOperation($request);
48✔
93

94
        if ($operation && $this->provider) {
48✔
95
            if (null === $operation->canDeserialize() && $operation instanceof HttpOperation) {
×
96
                $operation = $operation->withDeserialize(\in_array($operation->getMethod(), ['POST', 'PUT', 'PATCH'], true));
×
97
            }
98

99
            if (!$operation->canDeserialize()) {
×
100
                return;
×
101
            }
102

103
            $data = $this->provider->provide($operation, $request->attributes->get('_api_uri_variables') ?? [], [
×
104
                'request' => $request,
×
105
                'uri_variables' => $request->attributes->get('_api_uri_variables') ?? [],
×
106
                'resource_class' => $operation->getClass(),
×
107
            ]);
×
108

109
            $request->attributes->set('data', $data);
×
110

111
            return;
×
112
        }
113

114
        // TODO: the code below needs to be removed in 4.x
115
        if (
116
            'DELETE' === $method
48✔
117
            || $request->isMethodSafe()
48✔
118
            || $request->attributes->get('_api_platform_disable_listeners')
48✔
119
        ) {
120
            return;
15✔
121
        }
122

123
        if ('api_platform.symfony.main_controller' === $operation?->getController()) {
33✔
124
            return;
3✔
125
        }
126

127
        if (!($operation?->canDeserialize() ?? true)) {
30✔
128
            return;
3✔
129
        }
130

131
        $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes);
27✔
132

133
        $format = $this->getFormat($request, $operation?->getInputFormats() ?? []);
27✔
134
        $data = $request->attributes->get('data');
18✔
135
        if (
136
            null !== $data
18✔
137
            && (
138
                'POST' === $method
18✔
139
                || 'PATCH' === $method
18✔
140
                || ('PUT' === $method && !($operation->getExtraProperties()['standard_put'] ?? false))
18✔
141
            )
142
        ) {
143
            $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $data;
6✔
144
        }
145
        try {
146
            $request->attributes->set(
18✔
147
                'data',
18✔
148
                $this->serializer->deserialize($request->getContent(), $context['resource_class'], $format, $context)
18✔
149
            );
18✔
150
        } catch (PartialDenormalizationException $e) {
3✔
151
            $violations = new ConstraintViolationList();
3✔
152
            foreach ($e->getErrors() as $exception) {
3✔
153
                if (!$exception instanceof NotNormalizableValueException) {
3✔
154
                    continue;
×
155
                }
156
                $message = (new Type($exception->getExpectedTypes() ?? []))->message;
3✔
157
                $parameters = [];
3✔
158
                if ($exception->canUseMessageForUser()) {
3✔
159
                    $parameters['hint'] = $exception->getMessage();
3✔
160
                }
161
                $violations->add(new ConstraintViolation($this->translator->trans($message, ['{{ type }}' => implode('|', $exception->getExpectedTypes() ?? [])], 'validators'), $message, $parameters, null, $exception->getPath(), null, null, Type::INVALID_TYPE_ERROR));
3✔
162
            }
163
            if (0 !== \count($violations)) {
3✔
164
                throw new ValidationException($violations);
3✔
165
            }
166
        }
167
    }
168

169
    /**
170
     * Extracts the format from the Content-Type header and check that it is supported.
171
     *
172
     * @throws UnsupportedMediaTypeHttpException
173
     */
174
    private function getFormat(Request $request, array $formats): string
175
    {
176
        /** @var ?string $contentType */
177
        $contentType = $request->headers->get('CONTENT_TYPE');
27✔
178
        if (null === $contentType || '' === $contentType) {
27✔
179
            throw new UnsupportedMediaTypeHttpException('The "Content-Type" header must exist.');
6✔
180
        }
181

182
        $formatMatcher = new FormatMatcher($formats);
21✔
183
        $format = $formatMatcher->getFormat($contentType);
21✔
184
        if (null === $format) {
21✔
185
            $supportedMimeTypes = [];
3✔
186
            foreach ($formats as $mimeTypes) {
3✔
187
                foreach ($mimeTypes as $mimeType) {
3✔
188
                    $supportedMimeTypes[] = $mimeType;
3✔
189
                }
190
            }
191

192
            throw new UnsupportedMediaTypeHttpException(sprintf('The content-type "%s" is not supported. Supported MIME types are "%s".', $contentType, implode('", "', $supportedMimeTypes)));
3✔
193
        }
194

195
        return $format;
18✔
196
    }
197
}
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

© 2025 Coveralls, Inc