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

api-platform / core / 7047364573

30 Nov 2023 01:45PM UTC coverage: 37.36% (-0.007%) from 37.367%
7047364573

push

github

web-flow
fix(openapi): entrypoint access vnd+openapi (#6012)

fixes #6010

3 of 3 new or added lines in 1 file covered. (100.0%)

3 existing lines in 1 file now uncovered.

10295 of 27556 relevant lines covered (37.36%)

21.1 hits per line

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

72.6
/src/Symfony/EventListener/AddFormatListener.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\Error as ErrorOperation;
18
use ApiPlatform\Metadata\HttpOperation;
19
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
20
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
21
use Negotiation\Exception\InvalidArgument;
22
use Negotiation\Negotiator;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpKernel\Event\RequestEvent;
25
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
26
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
27

28
/**
29
 * Chooses the format to use according to the Accept header and supported formats.
30
 *
31
 * @author Kévin Dunglas <dunglas@gmail.com>
32
 */
33
final class AddFormatListener
34
{
35
    use OperationRequestInitiatorTrait;
36

37
    public function __construct(private readonly Negotiator $negotiator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, private readonly array $formats = [], private readonly array $errorFormats = [], private readonly array $docsFormats = [], private readonly ?bool $eventsBackwardCompatibility = null) // @phpstan-ignore-line
38
    {
39
        $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
120✔
40
    }
41

42
    /**
43
     * Sets the applicable format to the HttpFoundation Request.
44
     *
45
     * @throws NotFoundHttpException
46
     * @throws NotAcceptableHttpException
47
     */
48
    public function onKernelRequest(RequestEvent $event): void
49
    {
50
        $request = $event->getRequest();
120✔
51
        $operation = $this->initializeOperation($request);
120✔
52

53
        if ('api_platform.action.entrypoint' === $request->attributes->get('_controller')) {
120✔
54
            return;
21✔
55
        }
56

57
        if ('api_platform.symfony.main_controller' === $operation?->getController() || $request->attributes->get('_api_platform_disable_listeners')) {
105✔
58
            return;
30✔
59
        }
60

61
        if ($operation instanceof ErrorOperation) {
79✔
62
            return;
×
63
        }
64

65
        if (!($request->attributes->has('_api_resource_class')
79✔
66
            || $request->attributes->getBoolean('_api_respond', false)
79✔
67
            || $request->attributes->getBoolean('_graphql', false)
79✔
68
        )) {
69
            return;
19✔
70
        }
71

72
        $formats = $operation?->getOutputFormats() ?? ('api_doc' === $request->attributes->get('_route') ? $this->docsFormats : $this->formats);
63✔
73

74
        $this->addRequestFormats($request, $formats);
63✔
75

76
        // Empty strings must be converted to null because the Symfony router doesn't support parameter typing before 3.2 (_format)
77
        if (null === $routeFormat = $request->attributes->get('_format') ?: null) {
63✔
78
            $flattenedMimeTypes = $this->flattenMimeTypes($formats);
51✔
79
            $mimeTypes = array_keys($flattenedMimeTypes);
51✔
80
        } elseif (!isset($formats[$routeFormat])) {
12✔
81
            if (!$request->attributes->get('data') instanceof \Exception) {
3✔
82
                throw new NotFoundHttpException(sprintf('Format "%s" is not supported', $routeFormat));
3✔
83
            }
84
            $this->setRequestErrorFormat($operation, $request);
×
85

86
            return;
×
87
        } else {
88
            $mimeTypes = Request::getMimeTypes($routeFormat);
9✔
89
            $flattenedMimeTypes = $this->flattenMimeTypes([$routeFormat => $mimeTypes]);
9✔
90
        }
91

92
        // First, try to guess the format from the Accept header
93
        /** @var string|null $accept */
94
        $accept = $request->headers->get('Accept');
60✔
95

96
        if (null !== $accept) {
60✔
97
            $mediaType = null;
48✔
98
            try {
99
                $mediaType = $this->negotiator->getBest($accept, $mimeTypes);
48✔
100
            } catch (InvalidArgument) {
3✔
101
                throw $this->getNotAcceptableHttpException($accept, $flattenedMimeTypes);
3✔
102
            }
103

104
            if (null === $mediaType) {
45✔
105
                if (!$request->attributes->get('data') instanceof \Exception) {
9✔
106
                    throw $this->getNotAcceptableHttpException($accept, $flattenedMimeTypes);
9✔
107
                }
108

109
                $this->setRequestErrorFormat($operation, $request);
×
110

111
                return;
×
112
            }
113
            $formatMatcher = new FormatMatcher($formats);
36✔
114
            $request->setRequestFormat($formatMatcher->getFormat($mediaType->getType()));
36✔
115

116
            return;
36✔
117
        }
118

119
        // Then use the Symfony request format if available and applicable
120
        $requestFormat = $request->getRequestFormat('') ?: null;
12✔
121
        if (null !== $requestFormat) {
12✔
122
            $mimeType = $request->getMimeType($requestFormat);
12✔
123

124
            if (isset($flattenedMimeTypes[$mimeType])) {
12✔
125
                return;
9✔
126
            }
127

128
            if ($request->attributes->get('data') instanceof \Exception) {
3✔
129
                $this->setRequestErrorFormat($operation, $request);
×
130

131
                return;
×
132
            }
133

134
            throw $this->getNotAcceptableHttpException($mimeType, $flattenedMimeTypes);
3✔
135
        }
136

137
        // Finally, if no Accept header nor Symfony request format is set, return the default format
UNCOV
138
        foreach ($formats as $format => $mimeType) {
×
UNCOV
139
            $request->setRequestFormat($format);
×
140

UNCOV
141
            return;
×
142
        }
143
    }
144

145
    /**
146
     * Adds the supported formats to the request.
147
     *
148
     * This is necessary for {@see Request::getMimeType} and {@see Request::getMimeTypes} to work.
149
     */
150
    private function addRequestFormats(Request $request, array $formats): void
151
    {
152
        foreach ($formats as $format => $mimeTypes) {
63✔
153
            $request->setFormat($format, (array) $mimeTypes);
63✔
154
        }
155
    }
156

157
    /**
158
     * Retries the flattened list of MIME types.
159
     */
160
    private function flattenMimeTypes(array $formats): array
161
    {
162
        $flattenedMimeTypes = [];
60✔
163
        foreach ($formats as $format => $mimeTypes) {
60✔
164
            foreach ($mimeTypes as $mimeType) {
60✔
165
                $flattenedMimeTypes[$mimeType] = $format;
60✔
166
            }
167
        }
168

169
        return $flattenedMimeTypes;
60✔
170
    }
171

172
    /**
173
     * Retrieves an instance of NotAcceptableHttpException.
174
     */
175
    private function getNotAcceptableHttpException(string $accept, array $mimeTypes): NotAcceptableHttpException
176
    {
177
        return new NotAcceptableHttpException(sprintf(
15✔
178
            'Requested format "%s" is not supported. Supported MIME types are "%s".',
15✔
179
            $accept,
15✔
180
            implode('", "', array_keys($mimeTypes))
15✔
181
        ));
15✔
182
    }
183

184
    public function setRequestErrorFormat(?HttpOperation $operation, Request $request): void
185
    {
186
        $errorResourceFormats = array_merge($operation?->getOutputFormats() ?? [], $operation?->getFormats() ?? [], $this->errorFormats);
×
187

188
        $flattened = $this->flattenMimeTypes($errorResourceFormats);
×
189
        if ($flattened[$accept = $request->headers->get('Accept')] ?? false) {
×
190
            $request->setRequestFormat($flattened[$accept]);
×
191

192
            return;
×
193
        }
194

195
        if (isset($errorResourceFormats['jsonproblem'])) {
×
196
            $request->setRequestFormat('jsonproblem');
×
197
            $request->setFormat('jsonproblem', $errorResourceFormats['jsonproblem']);
×
198

199
            return;
×
200
        }
201

202
        $request->setRequestFormat(array_key_first($errorResourceFormats));
×
203
    }
204
}
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