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

api-platform / core / 7142557150

08 Dec 2023 02:28PM UTC coverage: 36.003% (-1.4%) from 37.36%
7142557150

push

github

web-flow
fix(jsonld): remove link to ApiDocumentation when doc is disabled (#6029)

0 of 1 new or added line in 1 file covered. (0.0%)

2297 existing lines in 182 files now uncovered.

9992 of 27753 relevant lines covered (36.0%)

147.09 hits per line

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

58.9
/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;
2,496✔
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();
2,496✔
51
        $operation = $this->initializeOperation($request);
2,496✔
52

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

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

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

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

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

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

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

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

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

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

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

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

116
            return;
42✔
117
        }
118

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

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

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

131
                return;
×
132
            }
133

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

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

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

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

169
        return $flattenedMimeTypes;
501✔
170
    }
171

172
    /**
173
     * Retrieves an instance of NotAcceptableHttpException.
174
     */
175
    private function getNotAcceptableHttpException(string $accept, array $mimeTypes): NotAcceptableHttpException
176
    {
UNCOV
177
        return new NotAcceptableHttpException(sprintf(
×
UNCOV
178
            'Requested format "%s" is not supported. Supported MIME types are "%s".',
×
UNCOV
179
            $accept,
×
UNCOV
180
            implode('", "', array_keys($mimeTypes))
×
UNCOV
181
        ));
×
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