Completed
Push — master ( 555126...4de831 )
by Midori
24:13 queued 23:03
created

Api::__destruct()   B

Complexity

Conditions 10
Paths 38

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 14.9309

Importance

Changes 5
Bugs 2 Features 0
Metric Value
cc 10
eloc 32
c 5
b 2
f 0
nc 38
nop 0
dl 0
loc 45
ccs 19
cts 30
cp 0.6333
crap 14.9309
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace midorikocak\nano;
6
7
use Exception;
8
9
use function array_key_exists;
10
use function array_map;
11
use function array_shift;
12
use function array_values;
13
use function base64_decode;
14
use function count;
15
use function explode;
16
use function header;
17
use function http_response_code;
18
use function is_callable;
19
use function is_string;
20
use function json_encode;
21
use function parse_url;
22
use function preg_match;
23
use function preg_match_all;
24
use function preg_replace;
25
use function strncasecmp;
26
use function strtolower;
27
use function substr;
28
use function trim;
29
30
use const JSON_THROW_ON_ERROR;
31
use const PHP_URL_PATH;
32
33
class Api
34
{
35
    private array $endpoints = [];
36
    private array $wildcards = [];
37
38
    private string $origin = '*';
39
    private int $responseCode = 404;
40
41 12
    public function __construct($origin = '*')
42
    {
43 12
        $this->origin = $origin;
44 12
    }
45
46
    public function setPrefix(string $prefix): void
47
    {
48
        $prefixEndpoints = [];
49
        $prefix = trim($prefix, '/');
50
51
        foreach ($this->endpoints as $methodName => $item) {
52
            if (!isset($prefixEndpoints[$methodName])) {
53
                $prefixEndpoints[$methodName] = [];
54
            }
55
56
            foreach ($item as $key => $value) {
57
                $key = $key === '' ? $key : '/' . $key;
58
                $prefixEndpoints[$methodName][$prefix . $key] = $value;
59
            }
60
        }
61
62
        $this->endpoints = $prefixEndpoints;
63
64
        $prefixWildcards = array_map(
65
            static function ($item) use ($prefix) {
66
                return $prefix . '/' . $item;
67
            },
68
            $this->wildcards
69
        );
70
71
        $this->wildcards = $prefixWildcards;
72
    }
73
74
    private function isOptions(): bool
75
    {
76
        if ((($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'OPTIONS') && $this->checkOrigin()) {
77
            header('Access-Control-Max-Age: 1728000');
78
            header('Content-Length: 0');
79
            header('Content-Type: text/plain');
80
            http_response_code(200);
81
            return true;
82
        }
83
84
        header('Access-Control-Max-Age: 3600');
85
        header(
86
            'Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With'
87
        );
88
        return false;
89
    }
90
91
    private function checkOrigin(): bool
92
    {
93
        if ($this->origin !== '*' && !$_SERVER['HTTP_ORIGIN'] === $this->origin) {
0 ignored issues
show
introduced by
The condition ! $_SERVER['HTTP_ORIGIN'] === $this->origin is always false.
Loading history...
94
            header('HTTP/1.1 403 Access Forbidden');
95
            header('Content-Type: text/plain');
96
            return false;
97
        }
98
        return true;
99
    }
100
101 12
    public function __destruct()
102
    {
103 12
        header("Access-Control-Allow-Origin: $this->origin");
104 12
        header('Content-Type: application/json; charset=UTF-8');
105 12
        header('Access-Control-Allow-Methods: OPTIONS, POST, GET, PUT, DELETE');
106 12
        header('Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept');
107
108 12
        $method = strtolower($_SERVER['REQUEST_METHOD'] ?? 'GET');
109 12
        if ($method === 'options') {
110
            $this->isOptions();
111
        } else {
112 12
            $uri = trim(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/');
113
114
            // Ignore uri that starts .php file extension
115
            //$uri = preg_replace('/^(.*?\.php\/{0,1})/', '', $uri);
116 12
            if (!isset($this->endpoints[$method])) {
117 6
                $this->endpoints[$method] = [];
118
            }
119 12
            $compared = $this->compareAgainstWildcards($uri);
120 12
            if (isset($this->endpoints[$method][$uri])) {
121
                try {
122 3
                    $fn = $this->endpoints[$method][$uri];
123 3
                    $this->responseCode = 200;
124 3
                    $fn();
125
                } catch (Exception $e) {
126
                    echo json_encode($e->getMessage(), JSON_THROW_ON_ERROR, 512);
127 3
                    $this->responseCode = 404;
128
                }
129 9
            } elseif (!empty($compared)) {
130
                try {
131
                    if (!array_key_exists($compared['pattern'], $this->endpoints[$method])) {
132
                        throw new Exception('Not found');
133
                    }
134
                    $fn = $this->endpoints[$method][$compared['pattern']];
135
                    $this->responseCode = 200;
136
                    $fn(...$compared['values']);
137
                } catch (Exception $e) {
138
                    echo json_encode($e->getMessage(), JSON_THROW_ON_ERROR, 512);
139
                    $this->responseCode = 404;
140
                }
141
            }
142
        }
143
144 12
        if ($this->responseCode && ((string) http_response_code() === '200')) {
145 3
            http_response_code($this->responseCode);
146
        }
147 12
    }
148
149 12
    private function compareAgainstWildcards($uri): array
150
    {
151 12
        foreach ($this->wildcards as $wildcard) {
152 6
            $compareUri = $this->compareUri($uri, $wildcard);
153 6
            if (!empty($compareUri)) {
154
                return $compareUri;
155
            }
156
        }
157 12
        return [];
158
    }
159
160 6
    private function compareUri($uri, $pattern): array
161
    {
162
        // does url have brackets?
163 6
        $hasBrackets = preg_match_all('/{(.+)}/', $pattern, $vars);
164 6
        if ($hasBrackets) {
165 3
            $newPattern = preg_replace('/{.+?}/m', '([^/{}]+)', $pattern);
166 3
            $passesNewPattern = preg_match('~^' . $newPattern . '$~', $uri, $values);
167 3
            array_shift($values);
168 3
            if ($passesNewPattern) {
169
                return [
170
                    'pattern' => $pattern,
171
                    'uri' => $uri,
172
                    'vars' => $vars,
173
                    'values' => array_values($values),
174
                ];
175
            }
176
        }
177 6
        return [];
178
    }
179
180 6
    private function hasBrackets($uri): bool
181
    {
182 6
        return preg_match('/{(.*?)}/', $uri) !== false;
183
    }
184
185
    public function getResponseCode(int $code): void
186
    {
187
        $this->responseCode = $code;
188
    }
189
190 6
    public function auth(callable $fn, callable $login)
191
    {
192 6
        if (isset($_SERVER['HTTP_AUTHORIZATION']) && strncasecmp($_SERVER['HTTP_AUTHORIZATION'], 'basic ', 6) === 0) {
193
            $exploded = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)), 2);
194
195
            if (count($exploded) === 2) {
196
                [$un, $pw] = $exploded;
197
            }
198
            if ($login($un ?? '', $pw ?? '')) {
199
                $fn();
200
            } else {
201
                $this->responseCode = 401;
202
            }
203
        }
204 6
    }
205
206 6
    public function __call($name, $arguments)
207
    {
208 6
        if (!isset($this->endpoints[$name])) {
209 6
            $this->endpoints[$name] = [];
210
        }
211
212 6
        if (count($arguments) !== 2) {
213
            return;
214
        }
215
216 6
        if (is_string($arguments[0]) && is_callable($arguments[1])) {
217 6
            $endpoint = parse_url(trim($arguments[0], '/'), PHP_URL_PATH);
218 6
            if ($this->hasBrackets($arguments[0])) {
219 6
                $this->wildcards[] = $endpoint;
220
            }
221 6
            $this->endpoints[$name][$endpoint] = $arguments[1];
222
        }
223 6
    }
224
}
225