1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Codeburner Framework. |
5
|
|
|
* |
6
|
|
|
* @author Alex Rohleder <[email protected]> |
7
|
|
|
* @copyright 2016 Alex Rohleder |
8
|
|
|
* @license http://opensource.org/licenses/MIT |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Codeburner\Router\Collectors; |
12
|
|
|
|
13
|
|
|
use Codeburner\Router\Collector; |
14
|
|
|
use Codeburner\Router\Group; |
15
|
|
|
use ReflectionClass; |
16
|
|
|
use ReflectionMethod; |
17
|
|
|
use ReflectionParameter; |
18
|
|
|
use Reflector; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Methods for enable the collector to make routes from a controller. |
22
|
|
|
* |
23
|
|
|
* @author Alex Rohleder <[email protected]> |
24
|
|
|
*/ |
25
|
|
|
|
26
|
|
|
trait ControllerCollectorTrait |
27
|
|
|
{ |
28
|
|
|
|
29
|
|
|
abstract public function getWildcards(); |
30
|
|
|
abstract public function set($method, $pattern, $action); |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Define how controller actions names will be joined to form the route pattern. |
34
|
|
|
* |
35
|
|
|
* @var string |
36
|
|
|
*/ |
37
|
|
|
|
38
|
|
|
protected $controllerActionJoin = "/"; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Maps all the controller methods that begins with a HTTP method, and maps the rest of |
42
|
|
|
* name as a path. The path will be the method name with slashes before every camelcased |
43
|
|
|
* word and without the HTTP method prefix, and the controller name will be used to prefix |
44
|
|
|
* the route pattern. e.g. ArticlesController::getCreate will generate a route to: GET articles/create |
45
|
|
|
* |
46
|
|
|
* @param string $controller The controller name |
47
|
|
|
* @param string $prefix |
48
|
|
|
* |
49
|
|
|
* @throws \ReflectionException |
50
|
|
|
* @return Group |
51
|
|
|
*/ |
52
|
|
|
|
53
|
5 |
|
public function controller($controller, $prefix = null) |
54
|
|
|
{ |
55
|
5 |
|
$controller = new ReflectionClass($controller); |
56
|
5 |
|
$prefix = $prefix === null ? $this->getControllerPrefix($controller) : $prefix; |
57
|
5 |
|
$methods = $controller->getMethods(ReflectionMethod::IS_PUBLIC); |
58
|
5 |
|
return $this->collectControllerRoutes($controller, $methods, "/$prefix/"); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Maps several controllers at same time. |
63
|
|
|
* |
64
|
|
|
* @param string[] $controllers Controllers name. |
65
|
|
|
* @throws \ReflectionException |
66
|
|
|
* @return Group |
67
|
|
|
*/ |
68
|
|
|
|
69
|
1 |
View Code Duplication |
public function controllers(array $controllers) |
|
|
|
|
70
|
|
|
{ |
71
|
1 |
|
$group = []; |
72
|
1 |
|
foreach ($controllers as $controller) |
73
|
1 |
|
$group[] = $this->controller($controller); |
74
|
1 |
|
return new Group($group); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Alias for Collector::controller but maps a controller without using the controller name as prefix. |
79
|
|
|
* |
80
|
|
|
* @param string $controller The controller name |
81
|
|
|
* @throws \ReflectionException |
82
|
|
|
* @return Group |
83
|
|
|
*/ |
84
|
|
|
|
85
|
2 |
|
public function controllerWithoutPrefix($controller) |
86
|
|
|
{ |
87
|
2 |
|
$controller = new ReflectionClass($controller); |
88
|
2 |
|
$methods = $controller->getMethods(ReflectionMethod::IS_PUBLIC); |
89
|
2 |
|
return $this->collectControllerRoutes($controller, $methods, "/"); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Alias for Collector::controllers but maps a controller without using the controller name as prefix. |
94
|
|
|
* |
95
|
|
|
* @param string[] $controllers |
96
|
|
|
* @throws \ReflectionException |
97
|
|
|
* @return Group |
98
|
|
|
*/ |
99
|
|
|
|
100
|
1 |
View Code Duplication |
public function controllersWithoutPrefix(array $controllers) |
|
|
|
|
101
|
|
|
{ |
102
|
1 |
|
$group = []; |
103
|
1 |
|
foreach ($controllers as $controller) |
104
|
1 |
|
$group[] = $this->controllerWithoutPrefix($controller); |
105
|
1 |
|
return new Group($group); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @param ReflectionClass $controller |
110
|
|
|
* @param string[] $methods |
111
|
|
|
* @param string $prefix |
112
|
|
|
* |
113
|
|
|
* @return Group |
114
|
|
|
*/ |
115
|
|
|
|
116
|
7 |
|
protected function collectControllerRoutes(ReflectionClass $controller, array $methods, $prefix) |
117
|
|
|
{ |
118
|
7 |
|
$group = []; |
119
|
7 |
|
$controllerDefaultStrategy = $this->getAnnotatedStrategy($controller); |
120
|
|
|
|
121
|
|
|
/** @var ReflectionMethod $method */ |
122
|
7 |
|
foreach ($methods as $method) { |
123
|
7 |
|
$name = preg_split("~(?=[A-Z])~", $method->name); |
124
|
7 |
|
$http = $name[0]; |
125
|
7 |
|
unset($name[0]); |
126
|
|
|
|
127
|
7 |
|
if (strpos(Collector::HTTP_METHODS, $http) !== false) { |
128
|
7 |
|
$action = $prefix . strtolower(implode($this->controllerActionJoin, $name)); |
129
|
7 |
|
$dynamic = $this->getMethodConstraints($method); |
|
|
|
|
130
|
7 |
|
$strategy = $this->getAnnotatedStrategy($method); |
|
|
|
|
131
|
|
|
|
132
|
|
|
/** @var \Codeburner\Router\Route $route */ |
133
|
7 |
|
$route = $this->set($http, "$action$dynamic", [$controller->name, $method->name]); |
134
|
|
|
|
135
|
7 |
|
if ($strategy !== null) { |
136
|
|
|
$route->setStrategy($strategy); |
137
|
7 |
|
} else $route->setStrategy($controllerDefaultStrategy); |
138
|
|
|
|
139
|
7 |
|
$group[] = $route; |
140
|
7 |
|
} |
141
|
7 |
|
} |
142
|
|
|
|
143
|
7 |
|
return new Group($group); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param ReflectionClass $controller |
148
|
|
|
* |
149
|
|
|
* @return string |
150
|
|
|
*/ |
151
|
|
|
|
152
|
4 |
|
protected function getControllerPrefix(ReflectionClass $controller) |
153
|
|
|
{ |
154
|
4 |
|
preg_match("~\@prefix\s([a-zA-Z\\\_]+)~i", (string) $controller->getDocComment(), $prefix); |
155
|
4 |
|
return isset($prefix[1]) ? $prefix[1] : str_replace("controller", "", strtolower($controller->getShortName())); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* @param \ReflectionMethod |
160
|
|
|
* @return string |
161
|
|
|
*/ |
162
|
|
|
|
163
|
7 |
|
protected function getMethodConstraints(ReflectionMethod $method) |
164
|
|
|
{ |
165
|
7 |
|
$beginPath = ""; |
166
|
7 |
|
$endPath = ""; |
167
|
|
|
|
168
|
7 |
|
if ($parameters = $method->getParameters()) { |
169
|
7 |
|
$types = $this->getParamsConstraint($method); |
170
|
|
|
|
171
|
7 |
|
foreach ($parameters as $parameter) { |
172
|
7 |
|
if ($parameter->isOptional()) { |
173
|
6 |
|
$beginPath .= "["; |
174
|
6 |
|
$endPath .= "]"; |
175
|
6 |
|
} |
176
|
|
|
|
177
|
7 |
|
$beginPath .= $this->getPathConstraint($parameter, $types); |
178
|
7 |
|
} |
179
|
7 |
|
} |
180
|
|
|
|
181
|
7 |
|
return $beginPath . $endPath; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* @param ReflectionParameter $parameter |
186
|
|
|
* @param string[] $types |
187
|
|
|
* @return string |
188
|
|
|
*/ |
189
|
|
|
|
190
|
7 |
|
protected function getPathConstraint(ReflectionParameter $parameter, $types) |
191
|
|
|
{ |
192
|
7 |
|
$name = $parameter->name; |
193
|
7 |
|
$path = "/{" . $name; |
194
|
7 |
|
return isset($types[$name]) ? "$path:{$types[$name]}}" : "$path}"; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @param ReflectionMethod $method |
199
|
|
|
* @return string[] |
200
|
|
|
*/ |
201
|
|
|
|
202
|
7 |
|
protected function getParamsConstraint(ReflectionMethod $method) |
203
|
|
|
{ |
204
|
7 |
|
$params = []; |
205
|
7 |
|
preg_match_all("~\@param\s(" . implode("|", array_keys($this->getWildcards())) . "|\(.+\))\s\\$([a-zA-Z0-1_]+)~i", |
206
|
7 |
|
$method->getDocComment(), $types, PREG_SET_ORDER); |
207
|
|
|
|
208
|
7 |
|
foreach ((array) $types as $type) { |
209
|
|
|
// if a pattern is defined on Match take it otherwise take the param type by PHPDoc. |
210
|
1 |
|
$params[$type[2]] = isset($type[4]) ? $type[4] : $type[1]; |
211
|
7 |
|
} |
212
|
|
|
|
213
|
7 |
|
return $params; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* @param ReflectionClass|ReflectionMethod $reflector |
218
|
|
|
* @return string|null |
219
|
|
|
*/ |
220
|
|
|
|
221
|
7 |
|
protected function getAnnotatedStrategy($reflector) |
222
|
|
|
{ |
223
|
7 |
|
preg_match("~\@strategy\s([a-zA-Z\\\_]+)~i", (string) $reflector->getDocComment(), $strategy); |
224
|
7 |
|
return isset($strategy[1]) ? $strategy[1] : null; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Define how controller actions names will be joined to form the route pattern. |
229
|
|
|
* Defaults to "/" so actions like "getMyAction" will be "/my/action". If changed to |
230
|
|
|
* "-" the new pattern will be "/my-action". |
231
|
|
|
* |
232
|
|
|
* @param string $join |
233
|
|
|
*/ |
234
|
|
|
|
235
|
1 |
|
public function setControllerActionJoin($join) |
236
|
|
|
{ |
237
|
1 |
|
$this->controllerActionJoin = $join; |
238
|
1 |
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @return string |
242
|
|
|
*/ |
243
|
|
|
|
244
|
|
|
public function getControllerActionJoin() |
245
|
|
|
{ |
246
|
|
|
return $this->controllerActionJoin; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
} |
250
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.