Passed
Push — refactor ( ba928a )
by Akihito
11:44
created

HttpMethodParams::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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