Passed
Pull Request — master (#1181)
by Tarmo
06:39 queued 03:09
created

ResponseHandler::getSerializeContext()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 18
nc 4
nop 2
dl 0
loc 36
ccs 16
cts 16
cp 1
crap 6
rs 9.0444
c 1
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/Rest/ResponseHandler.php
5
 *
6
 * @author TLe, Tarmo Leppänen <[email protected]>
7
 */
8
9
namespace App\Rest;
10
11
use App\Rest\Interfaces\ResponseHandlerInterface;
12
use App\Rest\Interfaces\RestResourceInterface;
13
use Symfony\Component\Form\FormError;
14
use Symfony\Component\Form\FormInterface;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\HttpKernel\Exception\HttpException;
18
use Symfony\Component\Serializer\SerializerInterface;
19
use Throwable;
20
use function array_key_exists;
21
use function array_map;
22
use function array_merge;
23
use function array_pop;
24
use function end;
25
use function explode;
26
use function implode;
27
use function sprintf;
28
use function strncmp;
29
30
/**
31
 * Class ResponseHandler
32
 *
33
 * @package App\Rest
34
 * @author TLe, Tarmo Leppänen <[email protected]>
35
 */
36
final class ResponseHandler implements ResponseHandlerInterface
37
{
38
    /**
39
     * Content types for supported response output formats.
40
     *
41
     * @var array<string, string>
42
     */
43
    private array $contentTypes = [
44
        self::FORMAT_JSON => 'application/json',
45
        self::FORMAT_XML => 'application/xml',
46
    ];
47
48 226
    public function __construct(
49
        private SerializerInterface $serializer,
50
    ) {
51 226
    }
52
53 1
    public function getSerializer(): SerializerInterface
54
    {
55 1
        return $this->serializer;
56
    }
57
58
    /**
59
     * @return array<int|string, mixed>
60
     *
61
     * @throws Throwable
62
     */
63 39
    public function getSerializeContext(Request $request, ?RestResourceInterface $restResource = null): array
64
    {
65
        /**
66
         * Specify used populate settings
67
         *
68
         * @var array<int, string>
69
         */
70 39
        $populate = (array)$request->get('populate', []);
71
72 39
        $groups = array_merge(['default', $populate]);
73
74 39
        if ($restResource !== null) {
75
            // Get current entity name
76 38
            $bits = explode('\\', $restResource->getEntityName());
77 38
            $entityName = end($bits);
78
79 38
            $populate = $this->checkPopulateAll(
80 38
                array_key_exists('populateAll', $request->query->all()),
81
                $populate,
82
                $entityName,
83
                $restResource
84
            );
85
86 38
            $groups = array_merge([$entityName], $populate);
87 38
            $filter = static fn (string $groupName): bool => strncmp($groupName, 'Set.', 4) === 0;
88
89 38
            if (array_key_exists('populateOnly', $request->query->all())
90 38
                || !empty(array_filter($groups, $filter))
91
            ) {
92 3
                $groups = empty($populate) ? [$entityName] : $populate;
93
            }
94
        }
95
96 39
        return array_merge(
97 39
            ['groups' => $groups],
98 39
            $restResource !== null ? $restResource->getSerializerContext() : [],
99
        );
100
    }
101
102
    /**
103
     * @throws Throwable
104
     */
105 35
    public function createResponse(
106
        Request $request,
107
        mixed $data,
108
        ?RestResourceInterface $restResource = null,
109
        ?int $httpStatus = null,
110
        ?string $format = null,
111
        ?array $context = null,
112
    ): Response {
113 35
        $httpStatus ??= 200;
114 35
        $context ??= $this->getSerializeContext($request, $restResource);
115 35
        $format = $this->getFormat($request, $format);
116 35
        $response = $this->getResponse($data, $httpStatus, $format, $context);
117
118
        // Set content type
119 30
        $response->headers->set('Content-Type', $this->contentTypes[$format]);
120
121 30
        return $response;
122
    }
123
124 2
    public function handleFormError(FormInterface $form): void
125
    {
126 2
        $errors = [];
127
128
        /** @var FormError $error */
129 2
        foreach ($form->getErrors(true) as $error) {
130 2
            $name = $error->getOrigin()?->getName() ?? '';
131
132 2
            $errors[] = sprintf(
133 2
                'Field \'%s\': %s',
134 2
                $name,
135 2
                $error->getMessage()
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on Symfony\Component\Form\FormErrorIterator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
                $error->/** @scrutinizer ignore-call */ 
136
                        getMessage()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
136
            );
137
138 2
            if ($name === '') {
139 1
                array_pop($errors);
140
141 1
                $errors[] = $error->getMessage();
142
            }
143
        }
144
145 2
        throw new HttpException(Response::HTTP_BAD_REQUEST, implode("\n", $errors));
146
    }
147
148
    /**
149
     * @param array<int, string> $populate
150
     *
151
     * @return array<int, string>
152
     *
153
     * @throws Throwable
154
     */
155 38
    private function checkPopulateAll(
156
        bool $populateAll,
157
        array $populate,
158
        string $entityName,
159
        RestResourceInterface $restResource,
160
    ): array {
161
        // Set all associations to be populated
162 38
        if ($populateAll && empty($populate)) {
163 2
            $associations = $restResource->getAssociations();
164 2
            $populate = array_map(
165 2
                static fn (string $assocName): string => $entityName . '.' . $assocName,
166 2
                $associations,
167
            );
168
        }
169
170 38
        return $populate;
171
    }
172
173
    /**
174
     * Getter method response format with fallback to default formats;
175
     *  - XML
176
     *  - JSON
177
     */
178 35
    private function getFormat(Request $request, ?string $format = null): string
179
    {
180 35
        return $format ?? ($request->getContentType() === self::FORMAT_XML ? self::FORMAT_XML : self::FORMAT_JSON);
181
    }
182
183
    /**
184
     * @param array<int|string, mixed> $context
185
     *
186
     * @throws HttpException
187
     */
188 35
    private function getResponse(mixed $data, int $httpStatus, string $format, array $context): Response
189
    {
190
        try {
191
            // Create new response
192 35
            $response = new Response();
193 35
            $response->setContent($this->serializer->serialize($data, $format, $context));
194 30
            $response->setStatusCode($httpStatus);
195 5
        } catch (Throwable $exception) {
196 5
            $status = Response::HTTP_BAD_REQUEST;
197
198 5
            throw new HttpException($status, $exception->getMessage(), $exception, [], $status);
199
        }
200
201 30
        return $response;
202
    }
203
}
204