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

api-platform / core / 17063025732

19 Aug 2025 07:44AM UTC coverage: 22.236% (+0.05%) from 22.188%
17063025732

push

github

soyuka
test: skip mongodb bundle when extension is not loaded

11683 of 52542 relevant lines covered (22.24%)

24.04 hits per line

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

95.16
/src/State/Processor/RespondProcessor.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\State\Processor;
15

16
use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
17
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
18
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
19
use ApiPlatform\Metadata\Exception\RuntimeException;
20
use ApiPlatform\Metadata\HttpOperation;
21
use ApiPlatform\Metadata\IriConverterInterface;
22
use ApiPlatform\Metadata\Operation;
23
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
24
use ApiPlatform\Metadata\Put;
25
use ApiPlatform\Metadata\ResourceClassResolverInterface;
26
use ApiPlatform\Metadata\UrlGeneratorInterface;
27
use ApiPlatform\Metadata\Util\ClassInfoTrait;
28
use ApiPlatform\Metadata\Util\CloneTrait;
29
use ApiPlatform\State\ProcessorInterface;
30
use ApiPlatform\State\StopwatchAwareInterface;
31
use ApiPlatform\State\StopwatchAwareTrait;
32
use Symfony\Component\HttpFoundation\Response;
33
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface as SymfonyHttpExceptionInterface;
34

35
/**
36
 * Serializes data.
37
 *
38
 * @author Kévin Dunglas <dunglas@gmail.com>
39
 */
40
final class RespondProcessor implements ProcessorInterface, StopwatchAwareInterface
41
{
42
    use ClassInfoTrait;
43
    use CloneTrait;
44
    use StopwatchAwareTrait;
45

46
    public const METHOD_TO_CODE = [
47
        'POST' => Response::HTTP_CREATED,
48
        'DELETE' => Response::HTTP_NO_CONTENT,
49
    ];
50

51
    public function __construct(
52
        private ?IriConverterInterface $iriConverter = null,
53
        private readonly ?ResourceClassResolverInterface $resourceClassResolver = null,
54
        private readonly ?OperationMetadataFactoryInterface $operationMetadataFactory = null,
55
    ) {
56
    }
545✔
57

58
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
59
    {
60
        if ($data instanceof Response || !$operation instanceof HttpOperation) {
544✔
61
            return $data;
34✔
62
        }
63

64
        if (!($request = $context['request'] ?? null)) {
512✔
65
            return $data;
×
66
        }
67

68
        $this->stopwatch?->start('api_platform.processor.respond');
512✔
69

70
        $headers = [
512✔
71
            'Content-Type' => \sprintf('%s; charset=utf-8', $request->getMimeType($request->getRequestFormat())),
512✔
72
            'Vary' => 'Accept',
512✔
73
            'X-Content-Type-Options' => 'nosniff',
512✔
74
            'X-Frame-Options' => 'deny',
512✔
75
        ];
512✔
76

77
        $exception = $request->attributes->get('exception');
512✔
78
        if (($exception instanceof HttpExceptionInterface || $exception instanceof SymfonyHttpExceptionInterface) && $exceptionHeaders = $exception->getHeaders()) {
512✔
79
            $headers = array_merge($headers, $exceptionHeaders);
2✔
80
        }
81

82
        if ($operationHeaders = $operation->getHeaders()) {
512✔
83
            $headers = array_merge($headers, $operationHeaders);
2✔
84
        }
85

86
        $status = $operation->getStatus();
512✔
87

88
        if ($sunset = $operation->getSunset()) {
512✔
89
            $headers['Sunset'] = (new \DateTimeImmutable($sunset))->format(\DateTimeInterface::RFC1123);
×
90
        }
91

92
        if ($acceptPatch = $operation->getAcceptPatch()) {
512✔
93
            $headers['Accept-Patch'] = $acceptPatch;
22✔
94
        }
95

96
        $method = $request->getMethod();
512✔
97
        $originalData = $context['original_data'] ?? null;
512✔
98

99
        $outputMetadata = $operation->getOutput() ?? ['class' => $operation->getClass()];
512✔
100
        $hasOutput = \is_array($outputMetadata) && \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class'];
512✔
101
        $hasData = !$hasOutput ? false : ($this->resourceClassResolver && $originalData && \is_object($originalData) && $this->resourceClassResolver->isResourceClass($this->getObjectClass($originalData)));
512✔
102

103
        if ($hasData) {
512✔
104
            $isAlternateResourceMetadata = $operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false;
188✔
105
            $canonicalUriTemplate = $operation->getExtraProperties()['canonical_uri_template'] ?? null;
188✔
106

107
            if (
108
                !isset($headers['Location'])
188✔
109
                && 300 <= $status && $status < 400
188✔
110
                && ($isAlternateResourceMetadata || $canonicalUriTemplate)
188✔
111
            ) {
112
                $canonicalOperation = $operation;
2✔
113
                if ($this->operationMetadataFactory && null !== $canonicalUriTemplate) {
2✔
114
                    $canonicalOperation = $this->operationMetadataFactory->create($canonicalUriTemplate, $context);
2✔
115
                }
116

117
                if ($this->iriConverter) {
2✔
118
                    $headers['Location'] = $this->iriConverter->getIriFromResource($originalData, UrlGeneratorInterface::ABS_PATH, $canonicalOperation);
2✔
119
                }
120
            } elseif ('PUT' === $method && !$request->attributes->get('previous_data') && null === $status && ($operation instanceof Put && ($operation->getAllowCreate() ?? false))) {
188✔
121
                $status = 201;
2✔
122
            }
123
        }
124

125
        $status ??= self::METHOD_TO_CODE[$method] ?? 200;
512✔
126

127
        $requestParts = parse_url($request->getRequestUri());
512✔
128
        if ($this->iriConverter && !isset($headers['Content-Location'])) {
512✔
129
            try {
130
                $iri = null;
508✔
131
                if ($hasData) {
508✔
132
                    $iri = $this->iriConverter->getIriFromResource($originalData);
188✔
133
                } elseif ($operation->getClass()) {
341✔
134
                    $iri = $this->iriConverter->getIriFromResource($operation->getClass(), UrlGeneratorInterface::ABS_PATH, $operation);
335✔
135
                }
136

137
                if ($iri && 'GET' !== $method) {
466✔
138
                    $location = \sprintf('%s.%s', $iri, $request->getRequestFormat());
14✔
139
                    if (isset($requestParts['query'])) {
14✔
140
                        $location .= '?'.$requestParts['query'];
×
141
                    }
142

143
                    $headers['Content-Location'] = $location;
14✔
144
                    if ((201 === $status || (300 <= $status && $status < 400)) && 'POST' === $method && !isset($headers['Location'])) {
14✔
145
                        $headers['Location'] = $iri;
466✔
146
                    }
147
                }
148
            } catch (InvalidArgumentException|ItemNotFoundException|RuntimeException) {
43✔
149
            }
150
        }
151

152
        $this->stopwatch?->stop('api_platform.processor.respond');
512✔
153

154
        return new Response(
512✔
155
            $data,
512✔
156
            $status,
512✔
157
            $headers
512✔
158
        );
512✔
159
    }
160
}
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

© 2025 Coveralls, Inc