Passed
Push — main ( d0ed98...da6fcc )
by Dimitri
13:43
created

ServerRequestFactory::fromGlobals()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 11
nc 1
nop 5
dl 0
loc 25
ccs 6
cts 6
cp 1
crap 1
rs 9.9
c 1
b 0
f 0
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\Http;
13
14
use BlitzPHP\Utilities\Iterable\Arr;
15
use InvalidArgumentException;
16
use Psr\Http\Message\ServerRequestFactoryInterface;
17
use Psr\Http\Message\ServerRequestInterface;
18
19
/**
20
 * Usine permettant de créer des instances de ServerRequest.
21
 *
22
 * Ceci ajoute un comportement spécifique à BlitzPHP pour remplir les attributs basePath et webroot.
23
 * En outre, le chemin de l'Uri est corrigé pour ne contenir que le chemin "virtuel" pour la requête.
24
 *
25
 * @credit CakePHP <a href="https://api.cakephp.org/5.0/class-Cake.Http.ServerRequestFactory.html">Cake\Http\ServerRequestFactory</a>
26
 * @credit <a href="https://docs.laminas.dev/laminas-diactoros/">Laminas\Diactoros</a>
27
 */
28
class ServerRequestFactory implements ServerRequestFactoryInterface
29
{
30
    /**
31
     * Créer une requête à partir des valeurs superglobales fournies.
32
     *
33
     * Si un argument n'est pas fourni, la valeur superglobale correspondante sera utilisée.
34
     *
35
     * @param array|null $server     superglobale $_SERVER
36
     * @param array|null $query      superglobale $_GET
37
     * @param array|null $parsedBody superglobale $_POST
38
     * @param array|null $cookies    superglobale $_COOKIE
39
     * @param array|null $files      superglobale $_FILES
40
     *
41
     * @throws InvalidArgumentException pour les valeurs de fichier non valides
42
     */
43
    public static function fromGlobals(
44
        ?array $server = null,
45
        ?array $query = null,
46
        ?array $parsedBody = null,
47
        ?array $cookies = null,
48
        ?array $files = null
49
    ): Request {
50 8
        $server = self::normalizeServer($server ?? $_SERVER);
51
52
        $request = new Request([
53
            'environment' => $server,
54
            'cookies'     => $cookies ?? $_COOKIE,
55
            'query'       => $query ?? $_GET,
56
            'session'     => service('session'),
57
            'input'       => $server['BLITZPHP_INPUT'] ?? null,
58 8
        ]);
59
60 8
        $request = static::processBodyAndRequestMethod($parsedBody ?? $_POST, $request);
61
        // Ceci est nécessaire car `ServerRequest::scheme()` ignore la valeur de `HTTP_X_FORWARDED_PROTO`
62
        // à moins que `trustProxy` soit activé, alors que l'instance `Uri` initialement créée prend
63
        // toujours en compte les valeurs de HTTP_X_FORWARDED_PROTO`.
64 8
        $uri     = $request->getUri()->withScheme($request->scheme());
0 ignored issues
show
Bug introduced by
It seems like $request->scheme() can also be of type null; however, parameter $scheme of Psr\Http\Message\UriInterface::withScheme() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

64
        $uri     = $request->getUri()->withScheme(/** @scrutinizer ignore-type */ $request->scheme());
Loading history...
65 8
        $request = $request->withUri($uri, true);
66
67 8
        return static::processFiles($files ?? $_FILES, $request);
68
    }
69
70
    /**
71
     * Définit la variable d'environnement REQUEST_METHOD en fonction de la valeur HTTP simulée _method.
72
     * La valeur 'ORIGINAL_REQUEST_METHOD' est également préservée, si vous souhaitez lire la méthode HTTP non simulée utilisée par le client.
73
     *
74
     * Le corps de la requête de type "application/x-www-form-urlencoded" est analysé dans un tableau pour les requêtes PUT/PATCH/DELETE.
75
     *
76
     * @param array $parsedBody Corps analysé.
77
     */
78
    protected static function processBodyAndRequestMethod(array $parsedBody, Request $request): Request
79
    {
80 8
        $method   = $request->getMethod();
81 8
        $override = false;
82
83
        if (in_array($method, ['PUT', 'DELETE', 'PATCH'], true) && str_starts_with((string) $request->contentType(), 'application/x-www-form-urlencoded')) {
84 2
            $data = (string) $request->getBody();
85 2
            parse_str($data, $parsedBody);
86
        }
87
        if ($request->hasHeader('X-Http-Method-Override')) {
88 2
            $parsedBody['_method'] = $request->getHeaderLine('X-Http-Method-Override');
89 2
            $override              = true;
90
        }
91
92 8
        $request = $request->withEnv('ORIGINAL_REQUEST_METHOD', $method);
93
        if (isset($parsedBody['_method'])) {
94 2
            $request = $request->withEnv('REQUEST_METHOD', $parsedBody['_method']);
95 2
            unset($parsedBody['_method']);
96 2
            $override = true;
97
        }
98
99
        if ($override && ! in_array($request->getMethod(), ['PUT', 'POST', 'DELETE', 'PATCH'], true)) {
100 2
            $parsedBody = [];
101
        }
102
103 8
        return $request->withParsedBody($parsedBody);
104
    }
105
106
    /**
107
     * Traiter les fichiers téléchargés et déplacer les éléments dans le corps analysé.
108
     *
109
     * @param array $files Tableau de fichiers pour la normalisation et la fusion dans le corps analysé.
110
     */
111
    protected static function processFiles(array $files, Request $request): Request
112
    {
113 8
        $files   = UploadedFileFactory::normalizeUploadedFiles($files);
114 8
        $request = $request->withUploadedFiles($files);
115
116 8
        $parsedBody = $request->getParsedBody();
117
        if (! is_array($parsedBody)) {
118
            return $request;
119
        }
120
121 8
        $parsedBody = Arr::merge($parsedBody, $files);
122
123 8
        return $request->withParsedBody($parsedBody);
124
    }
125
126
    /**
127
     * Créer une nouvelle requete de serveur.
128
     *
129
     * Notez que les paramètres du serveur sont pris tels quels - aucune analyse/traitement
130
     * des valeurs données n'est effectué, et, en particulier, aucune tentative n'est faite pour
131
     * déterminer la méthode HTTP ou l'URI, qui doivent être fournis explicitement.
132
     *
133
     * @param string                                $method       La méthode HTTP associée à la requete.
134
     * @param \Psr\Http\Message\UriInterface|string $uri          L'URI associé à la requete.
135
     *                                                            Si la valeur est une chaîne, la fabrique DOIT créer une instance d'UriInterface basée sur celle-ci.
136
     * @param array                                 $serverParams Tableau de paramètres SAPI permettant d'alimenter l'instance de requete générée.
137
     */
138
    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
139
    {
140 2
        $serverParams['REQUEST_METHOD'] = $method;
141 2
        $options                        = ['environment' => $serverParams];
142
143
        if (is_string($uri)) {
144 2
            $uri = new Uri($uri);
145
        }
146 2
        $options['uri'] = $uri;
147
148 2
        return new Request($options);
149
    }
150
151
    /**
152
     * Marshaller le tableau $_SERVER
153
     *
154
     * Prétraite et renvoie la superglobale $_SERVER.
155
     * En particulier, il tente de détecter l'en-tête Authorization, qui n'est souvent pas agrégé correctement sous diverses combinaisons SAPI/httpd.
156
     *
157
     * @param callable|null $apacheRequestHeaderCallback Callback qui peut être utilisé pour récupérer les en-têtes de requête Apache.
158
     *                                                   La valeur par défaut est `apache_request_headers` sous Apache mod_php.
159
     *
160
     * @see https://github.com/laminas/laminas-diactoros/blob/3.4.x/src/functions/normalize_server.php
161
     *
162
     * @return array Soit $server mot pour mot, soit avec un en-tête HTTP_AUTHORIZATION ajouté.
163
     */
164
    private static function normalizeServer(array $server, ?callable $apacheRequestHeaderCallback = null): array
165
    {
166
        if (null === $apacheRequestHeaderCallback && is_callable('apache_request_headers')) {
167 8
            $apacheRequestHeaderCallback = 'apache_request_headers';
168
        }
169
170
        // Si la valeur HTTP_AUTHORIZATION est déjà définie, ou si le callback n'est pas appelable, nous renvoyons les parameters server sans changements
171
        if (isset($server['HTTP_AUTHORIZATION']) || ! is_callable($apacheRequestHeaderCallback)) {
172 8
            return $server;
173
        }
174
175
        $apacheRequestHeaders = $apacheRequestHeaderCallback();
176
        if (isset($apacheRequestHeaders['Authorization'])) {
177
            $server['HTTP_AUTHORIZATION'] = $apacheRequestHeaders['Authorization'];
178
179
            return $server;
180
        }
181
182
        if (isset($apacheRequestHeaders['authorization'])) {
183
            $server['HTTP_AUTHORIZATION'] = $apacheRequestHeaders['authorization'];
184
185
            return $server;
186
        }
187
188
        return $server;
189
    }
190
}
191