Completed
Push — master ( 8a98eb...ce71d3 )
by Alejandro
02:13
created

Route::createRegexFromPattern()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 12
rs 9.4285
cc 2
eloc 10
nc 2
nop 0
1
<?php
2
3
namespace Tight;
4
5
/*
6
 * The MIT License
7
 *
8
 * Copyright 2016 Alejandro Peña Florentín ([email protected])
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in
18
 * all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
 * THE SOFTWARE.
27
 */
28
29
/**
30
 * Route class
31
 *
32
 * @author Alejandro Peña Florentín ([email protected])
33
 */
34
class Route
35
{
36
37
    const METHOD_GET = "get";
38
    const METHOD_POST = "post";
39
    const METHOD_UPDATE = "update";
40
    const METHOD_DELETE = "delete";
41
    const PARAMETER_CHARACTER = ":";
42
43
    protected $methods = [];
44
45
    /**
46
     * @var array Key-value array of URL parameters
47
     */
48
    protected $params = [];
49
50
    /**
51
     * @var array Middleware to be run before only this route instance
52
     */
53
    protected $middleware = [];
54
55
    /**
56
     * @var string The route pattern (e.g. "/books/:id")
57
     */
58
    protected $pattern = "";
59
    protected $callable;
60
    private $argsPattern = "(\\w+)";
61
62
    /**
63
     * Constructor.
64
     * 
65
     * @param string $pattern Pattern for the route
66
     * @param callable $callable Callable to be executed
67
     */
68
    public function __construct($pattern, $callable) {
69
        $this->argsPattern = self::PARAMETER_CHARACTER . $this->argsPattern;
70
        $this->setPattern($pattern);
71
        $this->setCallable($callable);
72
        $this->createRegexFromPattern();
73
    }
74
75
    /**
76
     * Transforms the pattern to a regular expresion.
77
     * This method also catches all the parameters in the pattern
78
     */
79
    private function createRegexFromPattern() {
80
        $pattern = $this->getPattern();
81
        $match = [];
82
        $pattern = str_replace("/", "\\/", $pattern);
83
        if (preg_match_all("/" . $this->argsPattern . "/", $pattern, $match)) {
84
            $strings = $match[0];
85
            $parameters = $match[1];
86
            $this->params = $parameters;
87
            $pattern = str_replace($strings, str_replace(self::PARAMETER_CHARACTER, "", $this->argsPattern), $pattern);
88
        }
89
        $this->setPattern("/^" . $pattern . "$/");
90
    }
91
92
    /**
93
     * Sets the route pattern
94
     * @param string $pattern Route pattern
95
     * @return \Tight\Route Fluent setter
96
     * @throws \InvalidArgumentException If $pattern is not a string
97
     */
98
    public function setPattern($pattern) {
99
        if (!is_string($pattern)) {
100
            throw new \InvalidArgumentException('Route name must be a string');
101
        }
102
        $this->pattern = $pattern;
103
        return $this;
104
    }
105
106
    /**
107
     * Sets the callable
108
     * @param callable $callback Callable to be executed
109
     */
110
    public function setCallable($callback) {
111
        if (is_callable($callback))
112
            $this->callable = $callback;
113
    }
114
115
    /**
116
     * Add supported HTTP method(s)
117
     */
118
    public function setHttpMethods($methods) {
119
        $this->methods = $methods;
120
    }
121
122
    /**
123
     * Get middleware
124
     * @return array Array of middleware
125
     */
126
    public function getMiddleware() {
127
        return $this->middleware;
128
    }
129
130
    /**
131
     * Set middleware
132
     *
133
     * This method allows middleware to be assigned to a specific Route.
134
     * If the method argument `is_callable` (including callable arrays!),
135
     * we directly append the argument to `$this->middleware`. Else, we
136
     * assume the argument is an array of callables and merge the array
137
     * with `$this->middleware`.  Each middleware is checked for is_callable()
138
     * and an InvalidArgumentException is thrown immediately if it isn't.
139
     *
140
     * @param  Callable|array
141
     * @return \Tight\Route Fluent setter
142
     * @throws \InvalidArgumentException If argument is not callable or not an array of callables.
143
     */
144
    public function setMiddleware($middleware) {
145
        if (is_callable($middleware)) {
146
            $this->middleware[] = $middleware;
147
        } elseif (is_array($middleware)) {
148
            foreach ($middleware as $callable) {
149
                if (!is_callable($callable)) {
150
                    throw new \InvalidArgumentException('All Route middleware must be callable');
151
                }
152
            }
153
            $this->middleware = array_merge($this->middleware, $middleware);
154
        } else {
155
            throw new \InvalidArgumentException('Route middleware must be callable or an array of callables');
156
        }
157
158
        return $this;
159
    }
160
161
    /**
162
     * Get supported HTTP methods
163
     * @return array HTTP methods
164
     */
165
    public function getHttpMethods() {
166
        return $this->methods;
167
    }
168
169
    /**
170
     * Get the pattern
171
     * @return string Pattern
172
     */
173
    public function getPattern() {
174
        return $this->pattern;
175
    }
176
177
    /**
178
     * Append supported HTTP methods
179
     */
180
    public function appendHttpMethods() {
181
        $args = func_get_args();
182
        $this->methods = array_merge($this->methods, $args);
183
    }
184
185
    /**
186
     * Get route parameters
187
     * @return array Route parameters
188
     */
189
    public function getParams() {
190
        return $this->params;
191
    }
192
193
    /**
194
     * Checks if an expresion match with this route
195
     * @param string $pattern Expresion to be matched
196
     * @return boolean TRUE on success
197
     */
198
    public function match($pattern) {
199
        $matches = [];
200
        if (preg_match_all($this->pattern, $pattern, $matches)) {
201
            $params = $this->params;
202
            // Remove 1st match, the full string
203
            unset($matches[0]);
204
            // Fix array index
205
            $matches = array_values($matches);
206
            // Clear the array parameter
207
            $this->params = [];
208
            $size = count($params);
209
            for ($index = 0; $index < $size; $index++) {
210
                $name = $params[$index];
211
                $value = $matches[$index][0];
212
                // Set parameter
213
                $this->params[$name] = $value;
214
            }
215
            return true;
216
        }
217
        return false;
218
    }
219
220
    /**
221
     * Dispatch route
222
     *
223
     * This method invokes the route object's callable. If middleware is
224
     * registered for the route, each callable middleware is invoked in
225
     * the order specified.
226
     *
227
     * @return bool
228
     */
229
    public function dispatch() {
230
        foreach ($this->middleware as $mw) {
231
            call_user_func_array($mw, array($this));
232
        }
233
        $return = call_user_func_array($this->callable, array_values($this->getParams()));
234
        return $return;
235
    }
236
237
}
238