1: <?php
2: /**
3: * Slim - a micro PHP 5 framework
4: *
5: * @author Josh Lockhart <info@slimframework.com>
6: * @copyright 2011 Josh Lockhart
7: * @link http://www.slimframework.com
8: * @license http://www.slimframework.com/license
9: * @version 2.2.0
10: * @package Slim
11: *
12: * MIT LICENSE
13: *
14: * Permission is hereby granted, free of charge, to any person obtaining
15: * a copy of this software and associated documentation files (the
16: * "Software"), to deal in the Software without restriction, including
17: * without limitation the rights to use, copy, modify, merge, publish,
18: * distribute, sublicense, and/or sell copies of the Software, and to
19: * permit persons to whom the Software is furnished to do so, subject to
20: * the following conditions:
21: *
22: * The above copyright notice and this permission notice shall be
23: * included in all copies or substantial portions of the Software.
24: *
25: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26: * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28: * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29: * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30: * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31: * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32: */
33: namespace Slim;
34:
35: /**
36: * Router
37: *
38: * This class organizes, iterates, and dispatches \Slim\Route objects.
39: *
40: * @package Slim
41: * @author Josh Lockhart
42: * @since 1.0.0
43: */
44: class Router
45: {
46: /**
47: * @var Route The current route (most recently dispatched)
48: */
49: protected $currentRoute;
50:
51: /**
52: * @var array Lookup hash of all route objects
53: */
54: protected $routes;
55:
56: /**
57: * @var array Lookup hash of named route objects, keyed by route name (lazy-loaded)
58: */
59: protected $namedRoutes;
60:
61: /**
62: * @var array Array of route objects that match the request URI (lazy-loaded)
63: */
64: protected $matchedRoutes;
65:
66: /**
67: * Constructor
68: */
69: public function __construct()
70: {
71: $this->routes = array();
72: }
73:
74: /**
75: * Get Current Route object or the first matched one if matching has been performed
76: * @return \Slim\Route|null
77: */
78: public function getCurrentRoute()
79: {
80: if ($this->currentRoute !== null) {
81: return $this->currentRoute;
82: }
83:
84: if (is_array($this->matchedRoutes) && count($this->matchedRoutes) > 0) {
85: return $this->matchedRoutes[0];
86: }
87:
88: return null;
89: }
90:
91: /**
92: * Return route objects that match the given HTTP method and URI
93: * @param string $httpMethod The HTTP method to match against
94: * @param string $resourceUri The resource URI to match against
95: * @param bool $reload Should matching routes be re-parsed?
96: * @return array[\Slim\Route]
97: */
98: public function getMatchedRoutes($httpMethod, $resourceUri, $reload = false)
99: {
100: if ($reload || is_null($this->matchedRoutes)) {
101: $this->matchedRoutes = array();
102: foreach ($this->routes as $route) {
103: if (!$route->supportsHttpMethod($httpMethod)) {
104: continue;
105: }
106:
107: if ($route->matches($resourceUri)) {
108: $this->matchedRoutes[] = $route;
109: }
110: }
111: }
112:
113: return $this->matchedRoutes;
114: }
115:
116: /**
117: * Map a route object to a callback function
118: * @param string $pattern The URL pattern (ie. "/books/:id")
119: * @param mixed $callable Anything that returns TRUE for is_callable()
120: * @return \Slim\Route
121: */
122: public function map($pattern, $callable)
123: {
124: $route = new \Slim\Route($pattern, $callable);
125: $this->routes[] = $route;
126:
127: return $route;
128: }
129:
130: /**
131: * Get URL for named route
132: * @param string $name The name of the route
133: * @param array Associative array of URL parameter names and replacement values
134: * @throws RuntimeException If named route not found
135: * @return string The URL for the given route populated with provided replacement values
136: */
137: public function urlFor($name, $params = array())
138: {
139: if (!$this->hasNamedRoute($name)) {
140: throw new \RuntimeException('Named route not found for name: ' . $name);
141: }
142: $search = array();
143: foreach (array_keys($params) as $key) {
144: $search[] = '#:' . $key . '\+?(?!\w)#';
145: }
146: $pattern = preg_replace($search, $params, $this->getNamedRoute($name)->getPattern());
147:
148: //Remove remnants of unpopulated, trailing optional pattern segments
149: return preg_replace('#\(/?:.+\)|\(|\)#', '', $pattern);
150: }
151:
152: /**
153: * Dispatch route
154: *
155: * This method invokes the route object's callable. If middleware is
156: * registered for the route, each callable middleware is invoked in
157: * the order specified.
158: *
159: * @param \Slim\Route $route The route object
160: * @return bool Was route callable invoked successfully?
161: */
162: public function dispatch(\Slim\Route $route)
163: {
164: $this->currentRoute = $route;
165:
166: //Invoke middleware
167: foreach ($route->getMiddleware() as $mw) {
168: call_user_func_array($mw, array($route));
169: }
170:
171: //Invoke callable
172: call_user_func_array($route->getCallable(), array_values($route->getParams()));
173:
174: return true;
175: }
176:
177: /**
178: * Add named route
179: * @param string $name The route name
180: * @param \Slim\Route $route The route object
181: * @throws \RuntimeException If a named route already exists with the same name
182: */
183: public function addNamedRoute($name, \Slim\Route $route)
184: {
185: if ($this->hasNamedRoute($name)) {
186: throw new \RuntimeException('Named route already exists with name: ' . $name);
187: }
188: $this->namedRoutes[(string) $name] = $route;
189: }
190:
191: /**
192: * Has named route
193: * @param string $name The route name
194: * @return bool
195: */
196: public function hasNamedRoute($name)
197: {
198: $this->getNamedRoutes();
199:
200: return isset($this->namedRoutes[(string) $name]);
201: }
202:
203: /**
204: * Get named route
205: * @param string $name
206: * @return \Slim\Route|null
207: */
208: public function getNamedRoute($name)
209: {
210: $this->getNamedRoutes();
211: if ($this->hasNamedRoute($name)) {
212: return $this->namedRoutes[(string) $name];
213: } else {
214: return null;
215: }
216: }
217:
218: /**
219: * Get named routes
220: * @return \ArrayIterator
221: */
222: public function getNamedRoutes()
223: {
224: if (is_null($this->namedRoutes)) {
225: $this->namedRoutes = array();
226: foreach ($this->routes as $route) {
227: if ($route->getName() !== null) {
228: $this->addNamedRoute($route->getName(), $route);
229: }
230: }
231: }
232:
233: return new \ArrayIterator($this->namedRoutes);
234: }
235: }
236: