Passed
Push — develop ( 36c2b4...1d782e )
by nguereza
03:02
created

BodyParserMiddleware::parseBody()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 17
c 1
b 0
f 0
nc 10
nop 1
dl 0
loc 32
rs 8.4444
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant PHP
7
 * Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file BodyParserMiddleware.php
34
 *
35
 *  This middleware is used to parse the request body like application/json
36
 *
37
 *  @package    Platine\Framework\Http\Middleware
38
 *  @author Platine Developers Team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   http://www.iacademy.cf
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Framework\Http\Middleware;
49
50
use Platine\Http\Handler\MiddlewareInterface;
51
use Platine\Http\Handler\RequestHandlerInterface;
52
use Platine\Http\ResponseInterface;
53
use Platine\Http\ServerRequestInterface;
54
use Platine\Stdlib\Helper\Json;
55
use RuntimeException;
56
57
/**
58
 * @class BodyParserMiddleware
59
 * @package Platine\Framework\Http\Middleware
60
 */
61
class BodyParserMiddleware implements MiddlewareInterface
62
{
63
64
    /**
65
     * List of body parser to use
66
     * @var array<string, callable>
67
     */
68
    protected array $parsers = [];
69
70
    /**
71
     * Create new instance
72
     */
73
    public function __construct()
74
    {
75
        $this->registerDefaultParsers();
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    public function process(
82
        ServerRequestInterface $request,
83
        RequestHandlerInterface $handler
84
    ): ResponseInterface {
85
        $parsedBody = $request->getParsedBody();
86
        if ($parsedBody === null || empty($parsedBody)) {
87
            $parsedBody = $this->parseBody($request);
88
89
            $request = $request->withParsedBody($parsedBody);
90
        }
91
92
        return $handler->handle($request);
93
    }
94
95
    /**
96
     * Register the parser for the given content type
97
     * @param string $contentType
98
     * @param callable $parser
99
     * @return $this
100
     */
101
    public function registerParser(string $contentType, callable $parser): self
102
    {
103
        $this->parsers[$contentType] = $parser;
104
105
        return $this;
106
    }
107
108
    /**
109
     * Whether the given content type have a registered parser
110
     * @param string $contentType
111
     * @return bool
112
     */
113
    public function hasParser(string $contentType): bool
114
    {
115
        return isset($this->parsers[$contentType]);
116
    }
117
118
    /**
119
     * Return the parser for the given content type
120
     * @param string $contentType
121
     * @return callable
122
     * @throws RuntimeException
123
     */
124
    public function getParser(string $contentType): callable
125
    {
126
        if (!isset($this->parsers[$contentType])) {
127
            throw new RuntimeException(sprintf(
128
                'Can not find the parser for the given content type [%s]',
129
                $contentType
130
            ));
131
        }
132
133
        return $this->parsers[$contentType];
134
    }
135
136
    /**
137
     * Register the default body parser
138
     * @return void
139
     */
140
    protected function registerDefaultParsers(): void
141
    {
142
        $this->registerParser('application/json', static function ($body) {
143
            return Json::decode($body, true);
144
        });
145
146
        $this->registerParser('application/x-www-form-urlencoded', static function ($body) {
147
            $data = [];
148
            parse_str($body, $data);
149
150
            return $data;
151
        });
152
    }
153
154
    /**
155
     * Parse the body
156
     * @param ServerRequestInterface $request
157
     * @return null|array<mixed>object
158
     */
159
    protected function parseBody(ServerRequestInterface $request)
160
    {
161
        $contentType = $this->getRequestContentType($request);
162
        if ($contentType === null) {
163
            return null;
164
        }
165
166
        if (!$this->hasParser($contentType)) {
167
             // If not, look for a media type with a structured syntax suffix (RFC 6839)
168
            $parts = expldoe('+', $contentType);
0 ignored issues
show
Bug introduced by
The function expldoe was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

168
            $parts = /** @scrutinizer ignore-call */ expldoe('+', $contentType);
Loading history...
169
170
            if (count($parts) >= 2) {
171
                $contentType = 'application/' . $parts[count($parts) - 1];
172
            }
173
        }
174
175
        if ($this->hasParser($contentType)) {
176
            $body = (string) $request->getBody();
177
            $parser = $this->getParser($contentType);
178
179
            $parsed = $parser($body);
180
            if (!is_null($parsed) && !is_array($parsed) && !is_object($parsed)) {
181
                throw new RuntimeException(sprintf(
182
                    'The return value of body parser must be an array, an object, or null, got [%s]',
183
                    gettype($parsed)
184
                ));
185
            }
186
187
            return $parsed;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $parsed also could return the type object which is incompatible with the documented return type array<mixed,mixed>|null.
Loading history...
188
        }
189
190
        return null;
191
    }
192
193
    /**
194
     * Return the request content type
195
     * @param ServerRequestInterface $request
196
     * @return string|null
197
     */
198
    protected function getRequestContentType(ServerRequestInterface $request): ?string
199
    {
200
        $contentTypes = $request->getHeader('Content-Type');
201
        $contentType = $contentTypes[0] ?? null;
202
203
        if (is_string($contentType) && trim($contentType) !== '') {
204
            $parts = explode(';', $contentType);
205
206
            return strtolower(trim($parts[0]));
207
        }
208
209
        return null;
210
    }
211
}
212