Passed
Push — master ( 8bd912...d93388 )
by Alan
06:58 queued 02:20
created

src/EventListener/DeserializeListener.php (1 issue)

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\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
19
use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait;
20
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
21
use ApiPlatform\Core\Util\RequestAttributesExtractor;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
24
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
25
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
26
use Symfony\Component\Serializer\SerializerInterface;
27
28
/**
29
 * Updates the entity retrieved by the data provider with data contained in the request body.
30
 *
31
 * @author Kévin Dunglas <[email protected]>
32
 */
33
final class DeserializeListener
34
{
35
    use ToggleableOperationAttributeTrait;
36
37
    public const OPERATION_ATTRIBUTE_KEY = 'deserialize';
38
39
    private $serializer;
40
    private $serializerContextBuilder;
41
    private $formats;
42
    private $formatsProvider;
43
44
    /**
45
     * @param ResourceMetadataFactoryInterface|FormatsProviderInterface|array $resourceMetadataFactory
46
     */
47
    public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, $resourceMetadataFactory, ResourceMetadataFactoryInterface $legacyResourceMetadataFactory = null)
48
    {
49
        $this->serializer = $serializer;
50
        $this->serializerContextBuilder = $serializerContextBuilder;
51
        $this->resourceMetadataFactory = $resourceMetadataFactory instanceof ResourceMetadataFactoryInterface ? $resourceMetadataFactory : $legacyResourceMetadataFactory;
52
53
        if (!$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) {
54
            @trigger_error(sprintf('Passing an array or an instance of "%s" as 3rd parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an instance of "%s" instead', FormatsProviderInterface::class, __CLASS__, ResourceMetadataFactoryInterface::class), E_USER_DEPRECATED);
55
        }
56
57
        if (\is_array($resourceMetadataFactory)) {
58
            $this->formats = $resourceMetadataFactory;
59
        } elseif ($resourceMetadataFactory instanceof FormatsProviderInterface) {
60
            $this->formatsProvider = $resourceMetadataFactory;
61
        }
62
    }
63
64
    /**
65
     * Deserializes the data sent in the requested format.
66
     *
67
     * @throws UnsupportedMediaTypeHttpException
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 ($this->resourceMetadataFactory) {
88
            $formats = $this
89
                ->resourceMetadataFactory
90
                ->create($attributes['resource_class'])
91
                ->getOperationAttribute($attributes, 'input_formats', [], true);
92
        } elseif ($this->formatsProvider instanceof FormatsProviderInterface) {
93
            $formats = $this->formatsProvider->getFormatsFromAttributes($attributes);
94
        } else {
95
            $formats = $this->formats;
96
        }
97
98
        $format = $this->getFormat($request, $formats);
99
        $data = $request->attributes->get('data');
100
        if (null !== $data) {
101
            $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $data;
102
        }
103
104
        $request->attributes->set(
105
            'data',
106
            $this->serializer->deserialize($request->getContent(), $context['resource_class'], $format, $context)
107
        );
108
    }
109
110
    /**
111
     * Extracts the format from the Content-Type header and check that it is supported.
112
     *
113
     * @throws UnsupportedMediaTypeHttpException
114
     */
115
    private function getFormat(Request $request, array $formats): string
116
    {
117
        /**
118
         * @var string|null
119
         */
120
        $contentType = $request->headers->get('CONTENT_TYPE');
121
        if (null === $contentType) {
0 ignored issues
show
The condition null === $contentType is always false.
Loading history...
122
            throw new UnsupportedMediaTypeHttpException('The "Content-Type" header must exist.');
123
        }
124
125
        $formatMatcher = new FormatMatcher($formats);
126
        $format = $formatMatcher->getFormat($contentType);
127
        if (null === $format) {
128
            $supportedMimeTypes = [];
129
            foreach ($formats as $mimeTypes) {
130
                foreach ($mimeTypes as $mimeType) {
131
                    $supportedMimeTypes[] = $mimeType;
132
                }
133
            }
134
135
            throw new UnsupportedMediaTypeHttpException(sprintf(
136
                'The content-type "%s" is not supported. Supported MIME types are "%s".',
137
                $contentType,
138
                implode('", "', $supportedMimeTypes)
139
            ));
140
        }
141
142
        return $format;
143
    }
144
}
145