HttpMethodParams::phpInput()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

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