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

api-platform / core / 7196499749

13 Dec 2023 02:17PM UTC coverage: 37.359% (+1.4%) from 36.003%
7196499749

push

github

web-flow
ci: conflict sebastian/comparator (#6032)

* ci: conflict sebastian/comparator

* for lowest

10295 of 27557 relevant lines covered (37.36%)

28.14 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;
160✔
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();
160✔
51
        $operation = $this->initializeOperation($request);
160✔
52

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

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

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

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

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

74
        $this->addRequestFormats($request, $formats);
79✔
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) {
79✔
78
            $flattenedMimeTypes = $this->flattenMimeTypes($formats);
63✔
79
            $mimeTypes = array_keys($flattenedMimeTypes);
63✔
80
        } elseif (!isset($formats[$routeFormat])) {
16✔
81
            if (!$request->attributes->get('data') instanceof \Exception) {
4✔
82
                throw new NotFoundHttpException(sprintf('Format "%s" is not supported', $routeFormat));
4✔
83
            }
84
            $this->setRequestErrorFormat($operation, $request);
×
85

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

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

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

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

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

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

116
            return;
43✔
117
        }
118

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

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

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

131
                return;
×
132
            }
133

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

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

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) {
79✔
153
            $request->setFormat($format, (array) $mimeTypes);
79✔
154
        }
155
    }
156

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

169
        return $flattenedMimeTypes;
75✔
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(
20✔
178
            'Requested format "%s" is not supported. Supported MIME types are "%s".',
20✔
179
            $accept,
20✔
180
            implode('", "', array_keys($mimeTypes))
20✔
181
        ));
20✔
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