HttpMethodParams::getOverrideMethod()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 18
ccs 6
cts 6
cp 1
rs 10
cc 3
nc 3
nop 3
crap 3
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 24
        private string $stdIn = 'php://input',
33
    ) {
34 24
    }
35 24
36
    /**
37
     * @psalm-api
38
     * @deprecated Use constructor injection
39
     */
40 29
    public function setStdIn(string $stdIn): void
41
    {
42
        $this->stdIn = $stdIn;
43 29
    }
44
45
    /**
46 29
     * {@inheritDoc}
47 4
     */
48
    #[Override]
49
    public function get(array $server, array $get, array $post)
50 25
    {
51
        // set the original value
52
        $method = strtolower($server['REQUEST_METHOD']);
53 25
54
        // early return on GET or HEAD
55 25
        if ($method === 'get' || $method === 'head') {
56
            return [$method, $get];
57 23
        }
58 12
59
        return $this->unsafeMethod($method, $server, $post);
60
    }
61 23
62
    /**
63
     * @param array{HTTP_X_HTTP_METHOD_OVERRIDE?: string, ...} $server
64 12
     * @param array<string, mixed>                        $post
65
     *
66
     * @return array{0: string, 1: array<string, mixed>}
67
     */
68
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
69 12
    private function unsafeMethod(string $method, array $server, array $post): array
70 3
    {
71 3
        /** @var array{_method?: string} $params */
72
        $params = $this->getParams($method, $server, $post);
73 3
74
        if ($method === 'post') {
75
            [$method, $params] = $this->getOverrideMethod($method, $server, $params);
76
        }
77 9
78 3
        return [$method, $params];
79
    }
80
81 9
    /**
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 25
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
88
    private function getOverrideMethod(string $method, array $server, array $params): array
89
    {
90 25
        // must be a POST to do an override
91 8
92
        // look for override in post data
93
        if (isset($params['_method'])) {
94 17
            $method = strtolower($params['_method']);
95 16
            unset($params['_method']);
96
97
            return [$method, $params];
98 1
        }
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 16
105
        return [$method, $params];
106 16
    }
107 16
108 16
    /**
109 6
     * Return request parameters
110
     *
111 6
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, ...} $server
112
     * @param array<string, mixed>                                     $post
113 10
     *
114 10
     * @return array<string, mixed>
115 4
     */
116
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
117 6
    private function getParams(string $method, array $server, array $post): array
118 6
    {
119 6
        // post data exists
120 2
        if ($method === 'post' && ! empty($post)) {
121
            return $post;
122
        }
123 4
124
        if (in_array($method, ['post', 'put', 'patch', 'delete'], true)) {
125
            return $this->phpInput($server);
126 12
        }
127
128 12
        return $post;
129
    }
130
131
    /**
132 12
     * Return request query by media-type
133
     *
134
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, ...} $server $_SERVER
135 16
     *
136
     * @return array<string, mixed>
137 16
     */
138 6
    // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamName
139
    private function phpInput(array $server): array
140 10
    {
141 8
        $contentType = $server[self::CONTENT_TYPE] ?? $server[self::HTTP_CONTENT_TYPE] ?? '';
142
        $isFormUrlEncoded = str_contains($contentType, self::FORM_URL_ENCODE);
143
        if ($isFormUrlEncoded) {
144 2
            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