Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.11% covered (warning)
61.11%
22 / 36
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RequestBodyDecoder
61.11% covered (warning)
61.11%
22 / 36
25.00% covered (danger)
25.00%
1 / 4
34.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDecoder
63.16% covered (warning)
63.16%
12 / 19
0.00% covered (danger)
0.00%
0 / 1
11.20
 decodeBody
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 addUploadedFiles
14.29% covered (danger)
14.29%
1 / 7
0.00% covered (danger)
0.00%
0 / 1
20.74
1<?php
2namespace Apie\Common;
3
4use Apie\Core\Exceptions\InvalidTypeException;
5use Apie\Core\FileStorage\StoredFile;
6use Apie\RestApi\Exceptions\InvalidContentTypeException;
7use Apie\Serializer\DecoderHashmap;
8use Apie\Serializer\Interfaces\DecoderInterface;
9use Psr\Http\Message\ServerRequestInterface;
10use Psr\Http\Message\UploadedFileInterface;
11
12final class RequestBodyDecoder
13{
14    public function __construct(
15        private readonly DecoderHashmap $decoderHashmap
16    ) {
17    }
18
19    public function getDecoder(ServerRequestInterface $request): ?DecoderInterface
20    {
21        if ($request->getMethod() === 'GET') {
22            return null;
23        }
24        $contentTypes = $request->getHeader('Content-Type');
25        if (count($contentTypes) !== 1) {
26            throw new InvalidContentTypeException($request->getHeaderLine('Content-Type'));
27        }
28        $contentType = reset($contentTypes);
29        $options = null;
30        $parsedBody = null;
31        if (strpos($contentType, ';') !== false) {
32            $options = strstr($contentType, ';', false);
33            $contentType = strstr($contentType, ';', true);
34        }
35        if (strtolower($contentType) === 'multipart/form-data') {
36            $parsedBody = $request->getParsedBody();
37        }
38        if (!isset($this->decoderHashmap[$contentType])) {
39            if ($request->getMethod() === 'DELETE') {
40                return null;
41            }
42            throw new InvalidContentTypeException($contentType);
43        }
44        $res = $this->decoderHashmap[$contentType]->withParsedBody($parsedBody);
45        return $options === null ? $res  : $res->withOptions($options);
46    }
47
48    /**
49     * @return array<string|int, mixed>
50     */
51    public function decodeBody(ServerRequestInterface $request): array
52    {
53        $decoder = $this->getDecoder($request);
54        
55        if (null === $decoder) {
56            return $request->getQueryParams();
57        }
58        $rawContents = $decoder->decode((string) $request->getBody());
59        if (!is_array($rawContents)) {
60            throw new InvalidTypeException($rawContents, 'array');
61        }
62        $uploadedFiles = $request->getUploadedFiles();
63        $this->addUploadedFiles($rawContents, $uploadedFiles['form'] ?? $uploadedFiles);
64        return $rawContents;
65    }
66
67    /**
68     * @param array<string|int, mixed> $rawContents
69     * @param array<string|int, mixed> $uploadedFiles
70     */
71    private function addUploadedFiles(array& $rawContents, array $uploadedFiles): void
72    {
73        foreach ($uploadedFiles as $key => $value) {
74            if ($value instanceof UploadedFileInterface) {
75                $rawContents[$key] = $value instanceof StoredFile ? $value : StoredFile::createFromUploadedFile($value);
76                continue;
77            }
78            if (!isset($rawContents[$key])) {
79                $rawContents[$key] = [];
80            }
81            $this->addUploadedFiles($rawContents[$key], $value);
82        }
83    }
84}