Completed
Push — 2.4 ( 09e800...d806c7 )
by Han Hui
21s queued 13s
created

DeserializeListener::getFormat()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 27
rs 9.2222
c 0
b 0
f 0
cc 6
nc 5
nop 1
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\EventListener;
15
16
use ApiPlatform\Core\Api\FormatMatcher;
17
use ApiPlatform\Core\Api\FormatsProviderInterface;
18
use ApiPlatform\Core\Exception\InvalidArgumentException;
19
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
20
use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait;
21
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
22
use ApiPlatform\Core\Util\RequestAttributesExtractor;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
25
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
26
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
27
use Symfony\Component\Serializer\SerializerInterface;
28
29
/**
30
 * Updates the entity retrieved by the data provider with data contained in the request body.
31
 *
32
 * @author Kévin Dunglas <[email protected]>
33
 */
34
final class DeserializeListener
35
{
36
    use ToggleableOperationAttributeTrait;
37
38
    public const OPERATION_ATTRIBUTE_KEY = 'deserialize';
39
40
    private $serializer;
41
    private $serializerContextBuilder;
42
    private $formats = [];
43
    private $formatsProvider;
44
    private $formatMatcher;
45
46
    /**
47
     * @throws InvalidArgumentException
48
     */
49
    public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, /* FormatsProviderInterface */$formatsProvider, ResourceMetadataFactoryInterface $resourceMetadataFactory = null)
50
    {
51
        $this->serializer = $serializer;
52
        $this->serializerContextBuilder = $serializerContextBuilder;
53
        if (\is_array($formatsProvider)) {
54
            @trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED);
55
            $this->formats = $formatsProvider;
56
        } else {
57
            if (!$formatsProvider instanceof FormatsProviderInterface) {
58
                throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class));
59
            }
60
61
            $this->formatsProvider = $formatsProvider;
62
        }
63
        $this->resourceMetadataFactory = $resourceMetadataFactory;
64
    }
65
66
    /**
67
     * Deserializes the data sent in the requested format.
68
     */
69
    public function onKernelRequest(GetResponseEvent $event): void
70
    {
71
        $request = $event->getRequest();
72
        $method = $request->getMethod();
73
74
        if (
75
            'DELETE' === $method
76
            || $request->isMethodSafe(false)
77
            || !($attributes = RequestAttributesExtractor::extractAttributes($request))
78
            || !$attributes['receive']
79
            || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY)
80
        ) {
81
            return;
82
        }
83
84
        $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes);
85
86
        // BC check to be removed in 3.0
87
        if (null !== $this->formatsProvider) {
88
            $this->formats = $this->formatsProvider->getFormatsFromAttributes($attributes);
89
        }
90
        $this->formatMatcher = new FormatMatcher($this->formats);
91
        $format = $this->getFormat($request);
92
93
        $data = $request->attributes->get('data');
94
        if (null !== $data) {
95
            $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $data;
96
        }
97
98
        $request->attributes->set(
99
            'data',
100
            $this->serializer->deserialize($request->getContent(), $context['resource_class'], $format, $context)
101
        );
102
    }
103
104
    /**
105
     * Extracts the format from the Content-Type header and check that it is supported.
106
     *
107
     * @throws NotAcceptableHttpException
108
     */
109
    private function getFormat(Request $request): string
110
    {
111
        /**
112
         * @var string|null
113
         */
114
        $contentType = $request->headers->get('CONTENT_TYPE');
115
        if (null === $contentType) {
0 ignored issues
show
introduced by
The condition null === $contentType is always false.
Loading history...
116
            throw new NotAcceptableHttpException('The "Content-Type" header must exist.');
117
        }
118
119
        $format = $this->formatMatcher->getFormat($contentType);
120
        if (null === $format || !isset($this->formats[$format])) {
121
            $supportedMimeTypes = [];
122
            foreach ($this->formats as $mimeTypes) {
123
                foreach ($mimeTypes as $mimeType) {
124
                    $supportedMimeTypes[] = $mimeType;
125
                }
126
            }
127
128
            throw new NotAcceptableHttpException(sprintf(
129
                'The content-type "%s" is not supported. Supported MIME types are "%s".',
130
                $contentType,
131
                implode('", "', $supportedMimeTypes)
132
            ));
133
        }
134
135
        return $format;
136
    }
137
}
138