Passed
Push — master ( 81d276...59fa1e )
by Kévin
04:12 queued 10s
created

RespondListener   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 46
c 0
b 0
f 0
dl 0
loc 89
rs 10
wmc 18

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B onKernelView() 0 46 10
B addAcceptPatchHeader() 0 23 7
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\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
17
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
18
use ApiPlatform\Core\Util\RequestAttributesExtractor;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
21
22
/**
23
 * Builds the response object.
24
 *
25
 * @author Kévin Dunglas <[email protected]>
26
 */
27
final class RespondListener
28
{
29
    public const METHOD_TO_CODE = [
30
        'POST' => Response::HTTP_CREATED,
31
        'DELETE' => Response::HTTP_NO_CONTENT,
32
    ];
33
34
    private $resourceMetadataFactory;
35
36
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory = null)
37
    {
38
        $this->resourceMetadataFactory = $resourceMetadataFactory;
39
    }
40
41
    /**
42
     * Creates a Response to send to the client according to the requested format.
43
     */
44
    public function onKernelView(GetResponseForControllerResultEvent $event): void
45
    {
46
        $controllerResult = $event->getControllerResult();
47
        $request = $event->getRequest();
48
49
        $attributes = RequestAttributesExtractor::extractAttributes($request);
50
        if ($controllerResult instanceof Response && ($attributes['respond'] ?? false)) {
51
            $event->setResponse($controllerResult);
52
53
            return;
54
        }
55
        if ($controllerResult instanceof Response || !($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond'))) {
56
            return;
57
        }
58
59
        $headers = [
60
            'Content-Type' => sprintf('%s; charset=utf-8', $request->getMimeType($request->getRequestFormat())),
61
            'Vary' => 'Accept',
62
            'X-Content-Type-Options' => 'nosniff',
63
            'X-Frame-Options' => 'deny',
64
        ];
65
66
        if ($request->attributes->has('_api_write_item_iri')) {
67
            $headers['Content-Location'] = $request->attributes->get('_api_write_item_iri');
68
69
            if ($request->isMethod('POST')) {
70
                $headers['Location'] = $request->attributes->get('_api_write_item_iri');
71
            }
72
        }
73
74
        $status = null;
75
        if ($this->resourceMetadataFactory && $attributes) {
0 ignored issues
show
introduced by
$attributes is an empty array, thus is always false.
Loading history...
Bug Best Practice introduced by
The expression $attributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
76
            $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
77
78
            if ($sunset = $resourceMetadata->getOperationAttribute($attributes, 'sunset', null, true)) {
79
                $headers['Sunset'] = (new \DateTimeImmutable($sunset))->format(\DateTime::RFC1123);
80
            }
81
82
            $headers = $this->addAcceptPatchHeader($headers, $attributes, $resourceMetadata);
83
            $status = $resourceMetadata->getOperationAttribute($attributes, 'status');
84
        }
85
86
        $event->setResponse(new Response(
87
            $controllerResult,
88
            $status ?? self::METHOD_TO_CODE[$request->getMethod()] ?? Response::HTTP_OK,
89
            $headers
90
        ));
91
    }
92
93
    private function addAcceptPatchHeader(array $headers, array $attributes, ResourceMetadata $resourceMetadata): array
94
    {
95
        if (!isset($attributes['item_operation_name'])) {
96
            return $headers;
97
        }
98
99
        $patchMimeTypes = [];
100
        foreach ($resourceMetadata->getItemOperations() as $operation) {
101
            if ('PATCH' !== ($operation['method'] ?? '') || !isset($operation['input_formats'])) {
102
                continue;
103
            }
104
105
            foreach ($operation['input_formats'] as $mimeTypes) {
106
                foreach ($mimeTypes as $mimeType) {
107
                    $patchMimeTypes[] = $mimeType;
108
                }
109
            }
110
            $headers['Accept-Patch'] = implode(', ', $patchMimeTypes);
111
112
            return $headers;
113
        }
114
115
        return $headers;
116
    }
117
}
118