HttpMethodParams::setStdIn()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
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 Override;
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 str_contains;
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
    public const HTTP_CONTENT_TYPE = 'HTTP_CONTENT_TYPE';
27
    public const FORM_URL_ENCODE = 'application/x-www-form-urlencoded';
28
    public const APPLICATION_JSON = 'application/json';
29
30
    public function __construct(
31
        #[StdIn]
32
        private string $stdIn = 'php://input',
33
    ) {
34
    }
35
36
    /**
37
     * @psalm-api
38
     * @deprecated Use constructor injection
39
     */
40
    public function setStdIn(string $stdIn): void
41
    {
42
        $this->stdIn = $stdIn;
43
    }
44
45
    /**
46
     * {@inheritDoc}
47
     */
48
    #[Override]
49
    public function get(array $server, array $get, array $post)
50
    {
51
        // set the original value
52
        $method = strtolower($server['REQUEST_METHOD']);
53
54
        // early return on GET or HEAD
55
        if ($method === 'get' || $method === 'head') {
56
            return [$method, $get];
57
        }
58
59
        return $this->unsafeMethod($method, $server, $post);
60
    }
61
62
    /**
63
     * @param array{HTTP_X_HTTP_METHOD_OVERRIDE?: string, ...} $server
64
     * @param array<string, mixed>                        $post
65
     *
66
     * @return array{0: string, 1: array<string, mixed>}
67
     */
68
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
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>}
86
     */
87
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
88
    private function getOverrideMethod(string $method, array $server, array $params): array
89
    {
90
        // must be a POST to do an override
91
92
        // look for override in post data
93
        if (isset($params['_method'])) {
94
            $method = strtolower($params['_method']);
95
            unset($params['_method']);
96
97
            return [$method, $params];
98
        }
99
100
        // look for override in headers
101
        if (isset($server['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
102
            $method = strtolower($server['HTTP_X_HTTP_METHOD_OVERRIDE']);
103
        }
104
105
        return [$method, $params];
106
    }
107
108
    /**
109
     * Return request parameters
110
     *
111
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, ...} $server
112
     * @param array<string, mixed>                                     $post
113
     *
114
     * @return array<string, mixed>
115
     */
116
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
117
    private function getParams(string $method, array $server, array $post): array
118
    {
119
        // post data exists
120
        if ($method === 'post' && ! empty($post)) {
121
            return $post;
122
        }
123
124
        if (in_array($method, ['post', 'put', 'patch', 'delete'], true)) {
125
            return $this->phpInput($server);
126
        }
127
128
        return $post;
129
    }
130
131
    /**
132
     * Return request query by media-type
133
     *
134
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, ...} $server $_SERVER
135
     *
136
     * @return array<string, mixed>
137
     */
138
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
139
    private function phpInput(array $server): array
140
    {
141
        $contentType = $server[self::CONTENT_TYPE] ?? $server[self::HTTP_CONTENT_TYPE] ?? '';
142
        $isFormUrlEncoded = str_contains($contentType, self::FORM_URL_ENCODE);
143
        if ($isFormUrlEncoded) {
144
            parse_str(rtrim($this->getRawBody($server)), $put);
145
146
            /** @var array<string, mixed> $put */
147
            return $put;
148
        }
149
150
        $isApplicationJson = str_contains($contentType, self::APPLICATION_JSON);
151
        if (! $isApplicationJson) {
152
            return [];
153
        }
154
155
        /** @var array<string, mixed> $content */
156
        $content = json_decode($this->getRawBody($server), true);
157
        $error = json_last_error();
158
        if ($error !== JSON_ERROR_NONE) {
159
            throw new InvalidRequestJsonException(json_last_error_msg());
160
        }
161
162
        return $content;
163
    }
164
165
    /** @param array{HTTP_RAW_POST_DATA?: string, ...} $server */
166
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
167
    private function getRawBody(array $server): string
168
    {
169
        return $server['HTTP_RAW_POST_DATA'] ?? rtrim((string) file_get_contents($this->stdIn));
170
    }
171
}
172