Passed
Push — master ( 30f743...a6a51d )
by Alexander
06:01
created

RequestBodyParser::ignoreBadRequestBody()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Request\Body;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\Http\Message\ResponseFactoryInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Yiisoft\Http\Header;
14
use Yiisoft\Request\Body\Parser\JsonParser;
15
16
final class RequestBodyParser implements MiddlewareInterface
17
{
18
    private ContainerInterface $container;
19
    private BadRequestHandlerInterface $badRequestHandler;
20
    private array $parsers = [
21
        'application/json' => JsonParser::class,
22
    ];
23
    private bool $ignoreBadRequestBody = false;
24
25 8
    public function __construct(
26
        ResponseFactoryInterface $responseFactory,
27
        ContainerInterface $container,
28
        BadRequestHandlerInterface $badRequestHandler = null
29
    ) {
30 8
        $this->container = $container;
31 8
        $this->badRequestHandler = $badRequestHandler ?? new BadRequestHandler($responseFactory);
32 8
    }
33
34 4
    public function withParser(string $mimeType, string $parserClass): self
35
    {
36 4
        $this->validateMimeType($mimeType);
37 3
        if ($parserClass === '') {
38
            throw new \InvalidArgumentException('The parser class cannot be an empty string.');
39
        }
40
41 3
        if ($this->container->has($parserClass) === false) {
42 1
            throw new \InvalidArgumentException("The parser \"$parserClass\" cannot be found.");
43
        }
44
45 2
        $new = clone $this;
46 2
        $new->parsers[$this->normalizeMimeType($mimeType)] = $parserClass;
47 2
        return $new;
48
    }
49
50 2
    public function withoutParsers(string ...$mimeTypes): self
51
    {
52 2
        $new = clone $this;
53 2
        if (count($mimeTypes) === 0) {
54 1
            $new->parsers = [];
55 1
            return $new;
56
        }
57 1
        foreach ($mimeTypes as $mimeType) {
58 1
            $this->validateMimeType($mimeType);
59 1
            unset($new->parsers[$this->normalizeMimeType($mimeType)]);
60
        }
61 1
        return $new;
62
    }
63
64 1
    public function ignoreBadRequestBody(): self
65
    {
66 1
        $new = clone $this;
67 1
        $new->ignoreBadRequestBody = true;
68 1
        return $new;
69
    }
70
71 6
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
72
    {
73 6
        $parser = $this->getParser($this->getContentType($request));
74 6
        if ($parser !== null) {
75
            try {
76 4
                $parsed = $parser->parse((string)$request->getBody());
77 1
                if ($parsed !== null && !is_object($parsed) && !is_array($parsed)) {
0 ignored issues
show
introduced by
The condition is_array($parsed) is always true.
Loading history...
78
                    $parserClass = get_class($parser);
79
                    throw new \RuntimeException(
80
                        "$parserClass::parse() return value must be an array, an object, or null."
81
                    );
82
                }
83 1
                $request = $request->withParsedBody($parsed);
84 3
            } catch (ParserException $e) {
85 3
                if (!$this->ignoreBadRequestBody) {
86 2
                    return $this->badRequestHandler->withParserException($e)->handle($request);
87
                }
88
            }
89
        }
90
91 4
        return $handler->handle($request);
92
    }
93
94 6
    private function getParser(?string $contentType): ?ParserInterface
95
    {
96 6
        if ($contentType !== null && array_key_exists($contentType, $this->parsers)) {
97 4
            return $this->container->get($this->parsers[$contentType]);
98
        }
99 2
        return null;
100
    }
101
102 6
    private function getContentType(ServerRequestInterface $request): ?string
103
    {
104 6
        $contentType = $request->getHeaderLine(Header::CONTENT_TYPE);
105 6
        if (is_string($contentType) && trim($contentType) !== '') {
106 6
            if (str_contains($contentType, ';')) {
107
                $contentTypeParts = explode(';', $contentType, 2);
108
                return strtolower(trim($contentTypeParts[0]));
109
            }
110 6
            return strtolower(trim($contentType));
111
        }
112
        return null;
113
    }
114
115
    /**
116
     * @throws \InvalidArgumentException
117
     */
118 5
    private function validateMimeType(string $mimeType): void
119
    {
120 5
        if (strpos($mimeType, '/') === false) {
121 1
            throw new \InvalidArgumentException('Invalid mime type.');
122
        }
123 4
    }
124
125 3
    private function normalizeMimeType(string $mimeType): string
126
    {
127 3
        return strtolower(trim($mimeType));
128
    }
129
}
130