Passed
Push — master ( c3a0e4...3147eb )
by Robson
02:11
created

Dispatch::dispatch()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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