Passed
Push — master ( 8e43c9...4981d1 )
by Tarmo
116:46 queued 51:34
created

ResponseHandler::handleFormError()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 5
nop 1
dl 0
loc 23
ccs 13
cts 13
cp 1
crap 4
rs 9.8666
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 count;
25
use function end;
26
use function explode;
27
use function implode;
28
use function sprintf;
29
use function strncmp;
30
31
/**
32
 * Class ResponseHandler
33
 *
34
 * @package App\Rest
35
 * @author TLe, Tarmo Leppänen <[email protected]>
36
 */
37
final class ResponseHandler implements ResponseHandlerInterface
38
{
39
    /**
40
     * Content types for supported response output formats.
41
     *
42
     * @var array<string, string>
43
     */
44
    private array $contentTypes = [
45
        self::FORMAT_JSON => 'application/json',
46
        self::FORMAT_XML => 'application/xml',
47
    ];
48
49
    public function __construct(
50
        private SerializerInterface $serializer,
51 193
    ) {
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ')', expecting T_VARIABLE on line 51 at column 4
Loading history...
52
    }
53 193
54 193
    public function getSerializer(): SerializerInterface
55
    {
56 1
        return $this->serializer;
57
    }
58 1
59
    /**
60
     * @return array<int|string, mixed>
61
     */
62
    public function getSerializeContext(Request $request, ?RestResourceInterface $restResource = null): array
63
    {
64 33
        /**
65
         * Specify used populate settings
66
         *
67
         * @var array<int, string>
68
         */
69
        $populate = (array)$request->get('populate', []);
70
71 33
        $groups = array_merge(['default', $populate]);
72
73 33
        if ($restResource !== null) {
74
            // Get current entity name
75 33
            $bits = explode('\\', $restResource->getEntityName());
76
            $entityName = end($bits);
77 32
78 32
            $populate = $this->checkPopulateAll(
79
                array_key_exists('populateAll', $request->query->all()),
80 32
                $populate,
81 32
                $entityName,
82
                $restResource
83
            );
84
85
            $groups = array_merge([$entityName], $populate);
86
            $filter = static fn (string $groupName): bool => strncmp($groupName, 'Set.', 4) === 0;
87 32
88 32
            if (array_key_exists('populateOnly', $request->query->all())
89
                || count(array_filter($groups, $filter)) > 0
90 32
            ) {
91 32
                $groups = count($populate) === 0 ? [$entityName] : $populate;
92
            }
93 3
        }
94
95
        return array_merge(
96
            ['groups' => $groups],
97 33
            $restResource !== null ? $restResource->getSerializerContext() : [],
98 33
        );
99 33
    }
100
101
    public function createResponse(
102
        Request $request,
103
        mixed $data,
104
        ?RestResourceInterface $restResource = null,
105
        ?int $httpStatus = null,
106 29
        ?string $format = null,
107
        ?array $context = null,
108
    ): Response {
109
        $httpStatus ??= 200;
110
        $context ??= $this->getSerializeContext($request, $restResource);
111
        $format = $this->getFormat($request, $format);
112
        $response = $this->getResponse($data, $httpStatus, $format, $context);
113
114 29
        // Set content type
115 29
        $response->headers->set('Content-Type', $this->contentTypes[$format]);
116 29
117
        return $response;
118 29
    }
119
120
    public function handleFormError(FormInterface $form): void
121 24
    {
122
        $errors = [];
123 24
124
        /** @var FormError $error */
125
        foreach ($form->getErrors(true) as $error) {
126 2
            $name = $error->getOrigin()?->getName() ?? '';
127
128 2
            $errors[] = sprintf(
129
                'Field \'%s\': %s',
130
                $name,
131 2
                $error->getMessage()
132 2
            );
133 2
134
            if ($name === '') {
135 2
                array_pop($errors);
136 2
137 2
                $errors[] = $error->getMessage();
138 2
            }
139
        }
140
141 2
        throw new HttpException(Response::HTTP_BAD_REQUEST, implode("\n", $errors));
142 1
    }
143
144 1
    /**
145
     * @param array<int, string> $populate
146
     *
147
     * @return array<int, string>
148 2
     */
149
    private function checkPopulateAll(
150
        bool $populateAll,
151
        array $populate,
152
        string $entityName,
153
        RestResourceInterface $restResource,
154
    ): array {
155
        // Set all associations to be populated
156 32
        if ($populateAll && count($populate) === 0) {
157
            $associations = $restResource->getAssociations();
158
            $populate = array_map(
159
                static fn (string $assocName): string => $entityName . '.' . $assocName,
160
                $associations,
161
            );
162
        }
163 32
164 2
        return $populate;
165 2
    }
166 2
167 2
    /**
168
     * Getter method response format with fallback to default formats;
169
     *  - XML
170
     *  - JSON
171 32
     */
172
    private function getFormat(Request $request, ?string $format = null): string
173
    {
174
        return $format ?? ($request->getContentType() === self::FORMAT_XML ? self::FORMAT_XML : self::FORMAT_JSON);
175
    }
176
177
    /**
178
     * @param array<int|string, mixed> $context
179 29
     *
180
     * @throws HttpException
181 29
     */
182
    private function getResponse(mixed $data, int $httpStatus, string $format, array $context): Response
183
    {
184
        try {
185
            // Create new response
186
            $response = new Response();
187
            $response->setContent($this->serializer->serialize($data, $format, $context));
188
            $response->setStatusCode($httpStatus);
189
        } catch (Throwable $exception) {
190 29
            $status = Response::HTTP_BAD_REQUEST;
191
192
            throw new HttpException($status, $exception->getMessage(), $exception, [], $status);
193
        }
194 29
195 29
        return $response;
196 24
    }
197
}
198