Dispatch::namespace()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace KadokWeb\Router;
4
5
6
/**
7
 * Class KadokWeb Dispatch
8
 * @author Doka B. Silva <https://github.com/kadokweb>
9
 * @package KadokWeb\Router
10
 */
11
abstract class Dispatch
12
{
13
    /** @var bool|string */
14
    protected $projectUrl;
15
16
    /** @var string */
17
    protected $patch;
18
19
    /** @var string */
20
    protected $separator;
21
22
    /** @var string */
23
    protected $httpMethod;
24
25
    /** @var array */
26
    protected $routes;
27
28
    /** @var null|string */
29
    protected $group;
30
31
    /** @var null|array */
32
    protected $route;
33
34
    /** @var null|string */
35
    protected $namespace;
36
37
    /** @var null|array */
38
    protected $data;
39
40
    /** @var int */
41
    protected $error;
42
43
    /** @const int Bad Request */
44
    public const BAD_REQUEST = 400;
45
46
    /** @const int Not Found */
47
    public const NOT_FOUND = 404;
48
49
    /** @const int Method Not Allowed */
50
    public const METHOD_NOT_ALLOWED = 405;
51
52
    /** @const int Not Implemented */
53
    public const NOT_IMPLEMENTED = 501;
54
55
    /**
56
     * Dispatch constructor.
57
     * @param string $projectUrl
58
     * @param string|null $separator
59
     */
60
    public function __construct(string $projectUrl, ?string $separator = ":")
61
    {
62
        $this->projectUrl = (substr($projectUrl, "-1") == "/" ? substr($projectUrl, 0, -1) : $projectUrl);
63
        $this->patch = (filter_input(INPUT_GET, "route", FILTER_DEFAULT) ?? "/");
64
        $this->separator = ($separator ?? ":");
65
        $this->httpMethod = $_SERVER['REQUEST_METHOD'];
66
    }
67
68
    /**
69
     * @return array
70
     */
71
    public function __debugInfo()
72
    {
73
        return $this->routes;
74
    }
75
76
    /**
77
     * @param null|string $group
78
     * @return Dispatch
79
     */
80
    public function group(?string $group): Dispatch
81
    {
82
        $this->group = ($group ? str_replace("/", "", $group) : null);
83
        return $this;
84
    }
85
86
    /**
87
     * @param null|string $namespace
88
     * @return Dispatch
89
     */
90
    public function namespace(?string $namespace): Dispatch
91
    {
92
        $this->namespace = ($namespace ? ucwords($namespace) : null);
93
        return $this;
94
    }
95
96
    /**
97
     * @return null|array
98
     */
99
    public function data(): ?array
100
    {
101
        return $this->data;
102
    }
103
104
    /**
105
     * @return null|int
106
     */
107
    public function error(): ?int
108
    {
109
        return $this->error;
110
    }
111
112
    /**
113
     * @param string $route
114
     */
115
    public function redirect(string $route): void
116
    {
117
        header("Location: {$this->projectUrl}{$route}");
118
    }
119
120
    /**
121
     * @return bool
122
     */
123
    public function dispatch(): bool
124
    {
125
        if (empty($this->routes) || empty($this->routes[$this->httpMethod])) {
126
            $this->error = self::NOT_IMPLEMENTED;
127
            return false;
128
        }
129
130
        $this->route = null;
131
        foreach ($this->routes[$this->httpMethod] as $key => $route) {
132
            if (preg_match("~^" . $key . "$~", $this->patch, $found)) {
133
                $this->route = $route;
134
            }
135
        }
136
137
        return $this->route();
138
    }
139
140
    /**
141
     * @return bool
142
     */
143
    private function route()
144
    {
145
        if ($this->route) {
146
            if (is_callable($this->route['handler'])) {
147
                call_user_func($this->route['handler'], ($this->route['data'] ?? []));
148
                return true;
149
            }
150
151
            $controller = $this->route['handler'];
152
            $method = $this->route['action'];
153
154
            if (class_exists($controller)) {
155
                $newController = new $controller;
156
                if (method_exists($controller, $method)) {
157
                    $newController->$method(($this->route['data'] ?? []));
158
                    return true;
159
                }
160
161
                $this->error = self::METHOD_NOT_ALLOWED;
162
                return false;
163
            }
164
165
            $this->error = self::BAD_REQUEST;
166
            return false;
167
        }
168
169
        $this->error = self::NOT_FOUND;
170
        return false;
171
    }
172
173
    /**
174
     * @param string $method
175
     * @param string $route
176
     * @param string|callable $handler
177
     * @return Dispatch
178
     */
179
    protected function addRoute(string $method, string $route, $handler): Dispatch
180
    {
181
        if ($route == "/") {
182
            $this->addRoute($method, "", $handler);
183
        }
184
185
        preg_match_all("~\{\s* ([a-zA-Z_][a-zA-Z0-9_-]*) \}~x", $route, $keys, PREG_SET_ORDER);
186
        $routeDiff = array_values(array_diff(explode("/", $this->patch), explode("/", $route)));
187
188
        $this->formSpoofing();
189
        $offset = ($this->group ? 1 : 0);
190
        foreach ($keys as $key) {
191
            $this->data[$key[1]] = ($routeDiff[$offset++] ?? null);
192
        }
193
194
        $route = (!$this->group ? $route : "/{$this->group}{$route}");
195
        $data = $this->data;
196
        $namespace = $this->namespace;
197
        $router = function () use ($method, $handler, $data, $route, $namespace) {
198
            return [
199
                "route" => $route,
200
                "method" => $method,
201
                "handler" => $this->handler($handler, $namespace),
202
                "action" => $this->action($handler),
203
                "data" => $data
204
            ];
205
        };
206
207
        $route = preg_replace('~{([^}]*)}~', "([^/]+)", $route);
208
        $this->routes[$method][$route] = $router();
209
        return $this;
210
    }
211
212
    /**
213
     * httpMethod form spoofing
214
     */
215
    protected function formSpoofing(): void
216
    {
217
        $post = filter_input_array(INPUT_POST, FILTER_DEFAULT);
218
219
        if (!empty($post['_method']) && in_array($post['_method'], ["PUT", "PATCH", "DELETE"])) {
220
            $this->httpMethod = $post['_method'];
221
            $this->data = $post;
222
223
            unset($this->data["_method"]);
224
            return;
225
        }
226
227
        if ($this->httpMethod == "POST") {
228
            $this->data = filter_input_array(INPUT_POST, FILTER_DEFAULT);
229
230
            unset($this->data["_method"]);
231
            return;
232
        }
233
234
        if (in_array($this->httpMethod, ["PUT", "PATCH", "DELETE"]) && !empty($_SERVER['CONTENT_LENGTH'])) {
235
            parse_str(file_get_contents('php://input', false, null, 0, $_SERVER['CONTENT_LENGTH']), $putPatch);
236
            $this->data = $putPatch;
237
238
            unset($this->data["_method"]);
239
            return;
240
        }
241
242
        $this->data = [];
243
        return;
244
    }
245
246
    /**
247
     * @param $handler
248
     * @param $namespace
249
     * @return string|callable
250
     */
251
    private function handler($handler, $namespace)
252
    {
253
        return (!is_string($handler) ? $handler : "{$namespace}\\" . explode($this->separator, $handler)[0]);
254
    }
255
256
    /**
257
     * @param $handler
258
     * @return null|string
259
     */
260
    private function action($handler): ?string
261
    {
262
        return (!is_string($handler) ?: (explode($this->separator, $handler)[1] ?? null));
263
    }
264
}