Passed
Push — main ( 2e91a7...db810f )
by Dimitri
03:15
created

BodyParser::init()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 17
rs 9.9332
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Middlewares;
13
14
use BlitzPHP\Exceptions\HttpException;
15
use BlitzPHP\Formatter\Formatter;
16
use Closure;
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use Psr\Http\Server\MiddlewareInterface;
20
use Psr\Http\Server\RequestHandlerInterface;
21
22
/**
23
 * BodyParser
24
 *
25
 * Analysez les données du corps de la requête encodée.
26
 * 
27
 * Permet aux charges utiles de requête JSON et XML d'être analysées dans la protection et la validation CSRF de la requête.
28
 *
29
 * Vous pouvez également ajouter vos propres analyseurs de corps de requête usi
30
 *
31
 * @credit		CakePHP (Cake\Http\Middleware\BodyParserMiddleware - https://cakephp.org)
32
 */
33
class BodyParser extends BaseMiddleware implements MiddlewareInterface
34
{
35
    /**
36
     * Parseurs enregistrés
37
     *
38
     * @var Closure[]
39
     */
40
    protected array $parsers = [];
41
42
    /**
43
     * Les méthodes HTTP sur lesquelles analyser les données.
44
     *
45
     * @var string[]
46
     */
47
    protected array $methods = ['PUT', 'POST', 'PATCH', 'DELETE'];
48
49
    /**
50
     * Constructor
51
     *
52
     * ### Options
53
     *
54
     * - `json` Définir sur false pour désactiver l'analyse du corps JSON.
55
     * - `xml` Définir sur true pour activer l'analyse XML. La valeur par défaut est false, en tant que XML
56
     * La manipulation nécessite plus de soin que JSON.
57
     * - `methods` Les méthodes HTTP à analyser. Par défaut, PUT, POST, PATCH DELETE.
58
     */
59
    public function init(array $options = []): self
60
    {
61
        $options += ['json' => true, 'xml' => false, 'methods' => null];
62
        if ($options['json']) {
63
            $this->addParser(
64
                ['application/json', 'text/json'],
65
                Closure::fromCallable([$this, 'decodeJson'])
66
            );
67
        }
68
        if ($options['xml']) {
69
            $this->addParser(
70
                ['application/xml', 'text/xml'],
71
                Closure::fromCallable([$this, 'decodeXml'])
72
            );
73
        }
74
75
        return parent::init($options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::init($options) returns the type BlitzPHP\Middlewares\BaseMiddleware which includes types incompatible with the type-hinted return BlitzPHP\Middlewares\BodyParser.
Loading history...
76
    }
77
78
    /**
79
     * Définissez les méthodes HTTP sur lesquelles analyser les corps de requête.
80
     *
81
     * @param string[] $methods Les méthodes sur lesquelles analyser les données.
82
     */
83
    public function setMethods(array $methods): self
84
    {
85
        $this->methods = $methods;
86
87
        return $this;
88
    }
89
90
    /**
91
     * Obtenez les méthodes HTTP pour analyser les corps de requête.
92
     *
93
     * @return string[]
94
     */
95
    public function getMethods(): array
96
    {
97
        return $this->methods;
98
    }
99
100
    /**
101
     * Ajoute un parser.
102
     *
103
     * Mappez un ensemble de valeurs d'en-tête de type de contenu à analyser par $parser.
104
     *
105
     * ### Example
106
     *
107
     * Un parseur de corps de requête CSV naïf pourrait être construit comme suit :
108
     *
109
     * ```
110
     * $parser->addParser(['text/csv'], function ($body) {
111
     *   return str_getcsv($body);
112
     * });
113
     * ```
114
     *
115
     * @param string[] $types  Un tableau de valeurs d'en-tête de type de contenu à faire correspondre. par exemple. application/json
116
     * @param Closure $parser La fonction de parser. Doit renvoyer un tableau de données à insérer dans la requête.
117
     */
118
    public function addParser(array $types, Closure $parser): self
119
    {
120
        foreach ($types as $type) {
121
            $type                 = strtolower($type);
122
            $this->parsers[$type] = $parser;
123
        }
124
125
        return $this;
126
    }
127
128
    /**
129
     * Obtenir les parseurs actuels
130
     *
131
     * @return Closure[]
132
     */
133
    public function getParsers(): array
134
    {
135
        return $this->parsers;
136
    }
137
138
    /**
139
     * {@inheritDoc}
140
     *
141
     * Modifie la requête en ajoutant un corps analysé si le type de contenu est connu.
142
     */
143
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
144
    {
145
        if (! in_array($request->getMethod(), $this->methods, true)) {
146
            return $handler->handle($request);
147
        }
148
149
        [$type] = explode(';', $request->getHeaderLine('Content-Type'));
150
        $type   = strtolower($type);
151
        if (! isset($this->parsers[$type])) {
152
            return $handler->handle($request);
153
        }
154
155
        $parser = $this->parsers[$type];
156
        $result = $parser($request->getBody()->getContents());
157
        if (! is_array($result)) {
158
            throw HttpException::badRequest();
159
        }
160
        $request = $request->withParsedBody($result);
161
162
        return $handler->handle($request);
163
    }
164
165
    /**
166
     * Décode JSON dans un tableau.
167
     *
168
     * @param string $body Le corps de la requête à décoder
169
     */
170
    protected function decodeJson(string $body): ?array
171
    {
172
        if ($body === '') {
173
            return [];
174
        }
175
        $decoded = json_decode($body, true);
176
        if (json_last_error() === JSON_ERROR_NONE) {
177
            return (array) $decoded;
178
        }
179
180
        return null;
181
    }
182
183
    /**
184
     * Décode XML dans un tableau.
185
     *
186
     * @param string $body Le corps de la requête à décoder
187
     */
188
    protected function decodeXml(string $body): array
189
    {
190
        return Formatter::type('application/xml')->parse($body);
191
    }
192
}
193