ResponseHandler::handleFormError()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 22
ccs 13
cts 13
cp 1
crap 3
rs 9.9
c 0
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 array_unique;
25
use function array_values;
26
use function end;
27
use function explode;
28
use function implode;
29
use function sprintf;
30
use function str_starts_with;
31
32
/**
33
 * Class ResponseHandler
34
 *
35
 * @package App\Rest
36
 * @author TLe, Tarmo Leppänen <[email protected]>
37
 */
38
class ResponseHandler implements ResponseHandlerInterface
39
{
40
    /**
41
     * Content types for supported response output formats.
42
     *
43
     * @var array<string, string>
44
     */
45
    private array $contentTypes = [
46
        self::FORMAT_JSON => 'application/json',
47
        self::FORMAT_XML => 'application/xml',
48
    ];
49
50 294
    public function __construct(
51
        private readonly SerializerInterface $serializer,
52
    ) {
53 294
    }
54
55 1
    public function getSerializer(): SerializerInterface
56
    {
57 1
        return $this->serializer;
58
    }
59
60
    /**
61
     * @return array<int|string, mixed>
62
     *
63
     * @throws Throwable
64
     */
65 105
    public function getSerializeContext(Request $request, ?RestResourceInterface $restResource = null): array
66
    {
67
        /**
68
         * Specify used populate settings
69
         *
70
         * @var array<int, string> $populate
71
         */
72 105
        $populate = (array)($request->query->get('populate') ?? $request->request->get('populate'));
73
74 105
        $groups = ['default', ...$populate];
75
76 105
        if ($restResource !== null) {
77
            // Get current entity name
78 104
            $bits = explode('\\', $restResource->getEntityName());
79 104
            $entityName = end($bits);
80
81 104
            $populate = $this->checkPopulateAll(
82 104
                array_key_exists('populateAll', $request->query->all()),
83 104
                $populate,
84 104
                $entityName,
85 104
                $restResource
86 104
            );
87
88 104
            $groups = [$entityName, ...$populate];
89 104
            $filter = static fn (string $groupName): bool => str_starts_with($groupName, 'Set.');
90
91 104
            if (array_key_exists('populateOnly', $request->query->all())
92 104
                || array_values(array_filter($groups, $filter)) !== []
93
            ) {
94 3
                $groups = $populate === [] ? [$entityName] : $populate;
95
            }
96
        }
97
98 105
        return array_merge(
99 105
            [
100 105
                'groups' => array_unique($groups),
101 105
            ],
102 105
            $restResource !== null ? $restResource->getSerializerContext() : [],
103 105
        );
104
    }
105
106
    /**
107
     * @throws Throwable
108
     */
109 101
    public function createResponse(
110
        Request $request,
111
        mixed $data,
112
        ?RestResourceInterface $restResource = null,
113
        ?int $httpStatus = null,
114
        ?string $format = null,
115
        ?array $context = null,
116
    ): Response {
117 101
        $httpStatus ??= 200;
118 101
        $context ??= $this->getSerializeContext($request, $restResource);
119 101
        $format = $this->getFormat($request, $format);
120 101
        $response = $this->getResponse($data, $httpStatus, $format, $context);
121
122
        // Set content type
123 96
        $response->headers->set('Content-Type', $this->contentTypes[$format]);
124
125 96
        return $response;
126
    }
127
128 2
    public function handleFormError(FormInterface $form): void
129
    {
130 2
        $errors = [];
131
132
        /** @var FormError $error */
133 2
        foreach ($form->getErrors(true) as $error) { // @phpstan-ignore-line
134 2
            $name = $error->getOrigin()?->getName() ?? '';
135
136 2
            $errors[] = sprintf(
137 2
                'Field \'%s\': %s',
138 2
                $name,
139 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

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