HttpMethodParams::phpInput()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 9.52
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Package\Provide\Router;
6
7
use BEAR\Package\Annotation\StdIn;
8
use BEAR\Package\Exception\InvalidRequestJsonException;
9
use Ray\Di\Di\Inject;
10
11
use function file_get_contents;
12
use function in_array;
13
use function json_decode;
14
use function json_last_error;
15
use function json_last_error_msg;
16
use function parse_str;
17
use function rtrim;
18
use function strpos;
19
use function strtolower;
20
21
use const JSON_ERROR_NONE;
22
23
final class HttpMethodParams implements HttpMethodParamsInterface
24
{
25
    public const CONTENT_TYPE = 'CONTENT_TYPE';
26
27
    public const HTTP_CONTENT_TYPE = 'HTTP_CONTENT_TYPE';
28
29
    public const FORM_URL_ENCODE = 'application/x-www-form-urlencoded';
30
31
    public const APPLICATION_JSON = 'application/json';
32
33
    /** @var string */
34
    private $stdIn = 'php://input';
35
36
    /**
37
     * @param string $stdIn
38
     *
39
     * @Inject(optional=true)
40
     * @StdIn
41
     */
42
    public function setStdIn($stdIn): void
43
    {
44
        $this->stdIn = $stdIn;
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function get(array $server, array $get, array $post)
51
    {
52
        // set the original value
53
        $method = strtolower($server['REQUEST_METHOD']);
54
55
        // early return on GET
56
        if ($method === 'get') {
57
            return ['get', $get];
58
        }
59
60
        return $this->unsafeMethod($method, $server, $post);
61
    }
62
63
    /**
64
     * @param array{HTTP_X_HTTP_METHOD_OVERRIDE?: string} $server
65
     * @param array<string, mixed>                        $post
66
     *
67
     * @return array{0: string, 1: array<string, mixed>}
0 ignored issues
show
Documentation introduced by
The doc-type array{0: could not be parsed: Unknown type name "array{0:" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
68
     */
69
    private function unsafeMethod(string $method, array $server, array $post): array
70
    {
71
        /** @var array{_method?: string} $params */
72
        $params = $this->getParams($method, $server, $post);
73
74
        if ($method === 'post') {
75
            [$method, $params] = $this->getOverrideMethod($method, $server, $params);
76
        }
77
78
        return [$method, $params];
79
    }
80
81
    /**
82
     * @param array{HTTP_X_HTTP_METHOD_OVERRIDE?: string} $server
83
     * @param array{_method?: string}                     $params
84
     *
85
     * @return array{0: string, 1: array<string, mixed>}
0 ignored issues
show
Documentation introduced by
The doc-type array{0: could not be parsed: Unknown type name "array{0:" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
86
     */
87
    private function getOverrideMethod(string $method, array $server, array $params): array
88
    {
89
        // must be a POST to do an override
90
91
        // look for override in post data
92
        if (isset($params['_method'])) {
93
            $method = strtolower($params['_method']);
94
            unset($params['_method']);
95
96
            return [$method, $params];
97
        }
98
99
        // look for override in headers
100
        if (isset($server['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
101
            $method = strtolower($server['HTTP_X_HTTP_METHOD_OVERRIDE']);
102
        }
103
104
        return [$method, $params];
105
    }
106
107
    /**
108
     * Return request parameters
109
     *
110
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string} $server
111
     * @param array<string, mixed>                                     $post
112
     *
113
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
114
     */
115
    private function getParams(string $method, array $server, array $post): array
116
    {
117
        // post data exists
118
        if ($method === 'post' && ! empty($post)) {
119
            return $post;
120
        }
121
122
        if (in_array($method, ['post', 'put', 'patch', 'delete'], true)) {
123
            return $this->phpInput($server);
124
        }
125
126
        return $post;
127
    }
128
129
    /**
130
     * Return request query by media-type
131
     *
132
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string} $server $_SERVER
133
     *
134
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
135
     */
136
    private function phpInput(array $server): array
137
    {
138
        $contentType = $server[self::CONTENT_TYPE] ?? $server[self::HTTP_CONTENT_TYPE] ?? '';
139
        $isFormUrlEncoded = strpos($contentType, self::FORM_URL_ENCODE) !== false;
140
        if ($isFormUrlEncoded) {
141
            parse_str(rtrim($this->getRawBody($server)), $put);
142
143
            /** @var array<string, mixed> $put */
144
            return $put;
145
        }
146
147
        $isApplicationJson = strpos($contentType, self::APPLICATION_JSON) !== false;
148
        if (! $isApplicationJson) {
149
            return [];
150
        }
151
152
        /** @var array<string, mixed> $content */
153
        $content = json_decode($this->getRawBody($server), true);
154
        $error = json_last_error();
155
        if ($error !== JSON_ERROR_NONE) {
156
            throw new InvalidRequestJsonException(json_last_error_msg());
157
        }
158
159
        return $content;
160
    }
161
162
    /**
163
     * @param array{HTTP_RAW_POST_DATA?: string} $server
164
     */
165
    private function getRawBody(array $server): string
166
    {
167
        return $server['HTTP_RAW_POST_DATA'] ?? rtrim((string) file_get_contents($this->stdIn));
168
    }
169
}
170