-
Notifications
You must be signed in to change notification settings - Fork 65
/
RoutingNode.class.php
192 lines (171 loc) · 5.85 KB
/
RoutingNode.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<?php
Library::import('recess.framework.routing.RoutingResult');
/**
* Routing nodes are used to build a routing tree which maps a requested
* URI string and HTTP Method to a Route. Example Route paths:
*
* /pages/ -> matches /pages/
* /pages/$id -> matches /pages/1 ... (id => 1)
* /pages/slug/$slug -> matches /pages/slug/some-slug-here (slug => some-slug-here)
*
* For the purposes of this class a URI path is broken into parts delimited
* with a '/'. There are two kinds of path parts: static and parametric. Static matches
* have precedence over parametric matches. For example, if you have the following routes:
*
* (1) /pages/$page_title/
* (2) /pages/a-page/
* (3) /pages/$page_title/$id
*
* A request of "/pages/a-page/" will match (2) and the result will not contain an argument.
* A request of "/pages/b-page/" will match (1) and the result will contain argument ("page_title" => "b_page")
* A request of "/pages/a-page/1" will match (3) with result arguments ("page_title" => "a_page", "id" => "1")
*
* @todo Add regular expression support to the parametric parts (/pages/:id(regexp-goes-here?)/)
*
* @author Kris Jordan <kris@krisjordan.com>
* @copyright Copyright (c) 2008, Kris Jordan
* @package recess.routing
*/
class RoutingNode {
protected $condition = '';
protected $methods = array();
protected $static_children = array();
protected $parametric_children = array();
/**
* Used to add a route to the routing tree.
*
* @param Route The route to add to this routing tree.
*/
public function addRoute($app, Route $route, $prefix) {
if($route->path == '') return;
$route->app = $app;
if($route->path[0] != '/') {
$route->path = $prefix . '/' . $route->path;
}
$pathParts = $this->getRevesedPathParts($route->path);
$this->addRouteRecursively($pathParts, count($pathParts) - 1, $route);
}
/**
* The recursive method powering addRouteFor(Request).
*
* @param array Part of a path in reverse order.
* @param int Current index of path part array - decrements with each step.
* @param Route The route being added
*
* @return FindRouteResult
*/
private function addRouteRecursively(&$pathParts, $index, $route) {
// Base Case
if($index < 0) {
foreach($route->methods as $method) {
if(isset($this->methods[$method])) {
throw new RecessException('Conflicting routes, the route: "' . $route->path . '" is defined twice.', get_defined_vars());
}
$this->methods[$method] = $route;
}
return;
}
$nextPart = $pathParts[$index];
if($nextPart[0] != '$') {
$childrenArray = &$this->static_children;
$nextKey = $nextPart;
$isParam = false;
} else {
$childrenArray = &$this->parametric_children;
$nextKey = substr($nextPart, 1);
$isParam = true;
}
if(!isset($childrenArray[$nextKey])) {
$child = new RoutingNode();
if($isParam) {
$child->condition = $nextKey;
}
$childrenArray[$nextKey] = $child;
} else {
$child = $childrenArray[$nextKey];
}
$child->addRouteRecursively($pathParts, $index - 1, $route);
}
/**
* Traverses children recursively to find a matching route. First looks
* to see if a static (non-parametric, i.e. /this_is_static/ vs. /$this_is_dynamic/)
* match exists. If not, we match against dynamic children. We reverse and step backwards
* through the array because $index > 0 is less costly than $index < count($parts)
* in PHP.
*
* @param Request The recess.http.Request object to find a matching route for.
*
* @return RoutingResult
*/
public function findRouteFor(Request $request) {
$pathParts = $this->getRevesedPathParts($request->resource);
return $this->findRouteRecursively($pathParts, count($pathParts) - 1, $request->method);
}
/**
* The recursive method powering findRouteFor(Request).
*
* @param array Part of a path in reverse order.
* @param int Current index of path part array - decrements with each step.
* @param string The HTTP METHOD desired for this route.
*
* @return RoutingResult
*/
private function findRouteRecursively(&$pathParts, $index, &$method) {
// Base Case - We've gone to the end of the path.
if($index < 0) {
$result = new RoutingResult();
if(!empty($this->methods)) { // Leaf, now check HTTP Method Match
if(isset($this->methods[$method])) {
$result->routeExists = true;
$result->methodIsSupported = true;
$result->route = $this->methods[$method];
} else {
$result->routeExists = true;
$result->route = array_values($this->methods);
$result->route = $result->route[0];
$result->methodIsSupported = false;
$result->acceptableMethods = array_keys($this->methods);
}
} else { // Non-leaf, no match
$result->routeExists = false;
}
return $result;
}
// Find a child for the next part of the path.
$nextPart = &$pathParts[$index];
$result = new RoutingResult();
// Check for a static match
if(isset($this->static_children[$nextPart])) {
$child = $this->static_children[$nextPart];
$result = $child->findRouteRecursively($pathParts, $index - 1, $method);
}
if(!$result->routeExists && !empty($this->parametric_children)) {
foreach($this->parametric_children as $child) {
if($child->matches($nextPart)) {
$result = $child->findRouteRecursively($pathParts, $index - 1, $method);
if($result->routeExists) {
if($child->condition != '') {
$result->arguments[$child->condition] = $nextPart;
}
return $result;
}
}
}
}
return $result;
}
public function matches($path) {
// TODO: Add regexp support
return $path != '';
}
// Helper Methods
/**
* Explodes a string by forward slashes, removes empty first/last node
* and finally reverses the array.
* @param string Path to be split and reversed.
*/
private function getRevesedPathParts($path) {
return array_reverse(array_filter(explode('/', $path)));
}
}
?>