Route.php (12791B)
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.4.2 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 * Route 37 * @package Slim 38 * @author Josh Lockhart, Thomas Bley 39 * @since 1.0.0 40 */ 41 class Route 42 { 43 /** 44 * @var string The route pattern (e.g. "/books/:id") 45 */ 46 protected $pattern; 47 48 /** 49 * @var mixed The route callable 50 */ 51 protected $callable; 52 53 /** 54 * @var array Conditions for this route's URL parameters 55 */ 56 protected $conditions = array(); 57 58 /** 59 * @var array Default conditions applied to all route instances 60 */ 61 protected static $defaultConditions = array(); 62 63 /** 64 * @var string The name of this route (optional) 65 */ 66 protected $name; 67 68 /** 69 * @var array Key-value array of URL parameters 70 */ 71 protected $params = array(); 72 73 /** 74 * @var array value array of URL parameter names 75 */ 76 protected $paramNames = array(); 77 78 /** 79 * @var array key array of URL parameter names with + at the end 80 */ 81 protected $paramNamesPath = array(); 82 83 /** 84 * @var array HTTP methods supported by this Route 85 */ 86 protected $methods = array(); 87 88 /** 89 * @var array[Callable] Middleware to be run before only this route instance 90 */ 91 protected $middleware = array(); 92 93 /** 94 * @var bool Whether or not this route should be matched in a case-sensitive manner 95 */ 96 protected $caseSensitive; 97 98 /** 99 * Constructor 100 * @param string $pattern The URL pattern (e.g. "/books/:id") 101 * @param mixed $callable Anything that returns TRUE for is_callable() 102 * @param bool $caseSensitive Whether or not this route should be matched in a case-sensitive manner 103 */ 104 public function __construct($pattern, $callable, $caseSensitive = true) 105 { 106 $this->setPattern($pattern); 107 $this->setCallable($callable); 108 $this->setConditions(self::getDefaultConditions()); 109 $this->caseSensitive = $caseSensitive; 110 } 111 112 /** 113 * Set default route conditions for all instances 114 * @param array $defaultConditions 115 */ 116 public static function setDefaultConditions(array $defaultConditions) 117 { 118 self::$defaultConditions = $defaultConditions; 119 } 120 121 /** 122 * Get default route conditions for all instances 123 * @return array 124 */ 125 public static function getDefaultConditions() 126 { 127 return self::$defaultConditions; 128 } 129 130 /** 131 * Get route pattern 132 * @return string 133 */ 134 public function getPattern() 135 { 136 return $this->pattern; 137 } 138 139 /** 140 * Set route pattern 141 * @param string $pattern 142 */ 143 public function setPattern($pattern) 144 { 145 $this->pattern = $pattern; 146 } 147 148 /** 149 * Get route callable 150 * @return mixed 151 */ 152 public function getCallable() 153 { 154 return $this->callable; 155 } 156 157 /** 158 * Set route callable 159 * @param mixed $callable 160 * @throws \InvalidArgumentException If argument is not callable 161 */ 162 public function setCallable($callable) 163 { 164 $matches = array(); 165 if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) { 166 $class = $matches[1]; 167 $method = $matches[2]; 168 $callable = function() use ($class, $method) { 169 static $obj = null; 170 if ($obj === null) { 171 $obj = new $class; 172 } 173 return call_user_func_array(array($obj, $method), func_get_args()); 174 }; 175 } 176 177 if (!is_callable($callable)) { 178 throw new \InvalidArgumentException('Route callable must be callable'); 179 } 180 181 $this->callable = $callable; 182 } 183 184 /** 185 * Get route conditions 186 * @return array 187 */ 188 public function getConditions() 189 { 190 return $this->conditions; 191 } 192 193 /** 194 * Set route conditions 195 * @param array $conditions 196 */ 197 public function setConditions(array $conditions) 198 { 199 $this->conditions = $conditions; 200 } 201 202 /** 203 * Get route name 204 * @return string|null 205 */ 206 public function getName() 207 { 208 return $this->name; 209 } 210 211 /** 212 * Set route name 213 * @param string $name 214 */ 215 public function setName($name) 216 { 217 $this->name = (string)$name; 218 } 219 220 /** 221 * Get route parameters 222 * @return array 223 */ 224 public function getParams() 225 { 226 return $this->params; 227 } 228 229 /** 230 * Set route parameters 231 * @param array $params 232 */ 233 public function setParams($params) 234 { 235 $this->params = $params; 236 } 237 238 /** 239 * Get route parameter value 240 * @param string $index Name of URL parameter 241 * @return string 242 * @throws \InvalidArgumentException If route parameter does not exist at index 243 */ 244 public function getParam($index) 245 { 246 if (!isset($this->params[$index])) { 247 throw new \InvalidArgumentException('Route parameter does not exist at specified index'); 248 } 249 250 return $this->params[$index]; 251 } 252 253 /** 254 * Set route parameter value 255 * @param string $index Name of URL parameter 256 * @param mixed $value The new parameter value 257 * @throws \InvalidArgumentException If route parameter does not exist at index 258 */ 259 public function setParam($index, $value) 260 { 261 if (!isset($this->params[$index])) { 262 throw new \InvalidArgumentException('Route parameter does not exist at specified index'); 263 } 264 $this->params[$index] = $value; 265 } 266 267 /** 268 * Add supported HTTP method(s) 269 */ 270 public function setHttpMethods() 271 { 272 $args = func_get_args(); 273 $this->methods = $args; 274 } 275 276 /** 277 * Get supported HTTP methods 278 * @return array 279 */ 280 public function getHttpMethods() 281 { 282 return $this->methods; 283 } 284 285 /** 286 * Append supported HTTP methods 287 */ 288 public function appendHttpMethods() 289 { 290 $args = func_get_args(); 291 if(count($args) && is_array($args[0])){ 292 $args = $args[0]; 293 } 294 $this->methods = array_merge($this->methods, $args); 295 } 296 297 /** 298 * Append supported HTTP methods (alias for Route::appendHttpMethods) 299 * @return \Slim\Route 300 */ 301 public function via() 302 { 303 $args = func_get_args(); 304 if(count($args) && is_array($args[0])){ 305 $args = $args[0]; 306 } 307 $this->methods = array_merge($this->methods, $args); 308 309 return $this; 310 } 311 312 /** 313 * Detect support for an HTTP method 314 * @param string $method 315 * @return bool 316 */ 317 public function supportsHttpMethod($method) 318 { 319 return in_array($method, $this->methods); 320 } 321 322 /** 323 * Get middleware 324 * @return array[Callable] 325 */ 326 public function getMiddleware() 327 { 328 return $this->middleware; 329 } 330 331 /** 332 * Set middleware 333 * 334 * This method allows middleware to be assigned to a specific Route. 335 * If the method argument `is_callable` (including callable arrays!), 336 * we directly append the argument to `$this->middleware`. Else, we 337 * assume the argument is an array of callables and merge the array 338 * with `$this->middleware`. Each middleware is checked for is_callable() 339 * and an InvalidArgumentException is thrown immediately if it isn't. 340 * 341 * @param Callable|array[Callable] 342 * @return \Slim\Route 343 * @throws \InvalidArgumentException If argument is not callable or not an array of callables. 344 */ 345 public function setMiddleware($middleware) 346 { 347 if (is_callable($middleware)) { 348 $this->middleware[] = $middleware; 349 } elseif (is_array($middleware)) { 350 foreach ($middleware as $callable) { 351 if (!is_callable($callable)) { 352 throw new \InvalidArgumentException('All Route middleware must be callable'); 353 } 354 } 355 $this->middleware = array_merge($this->middleware, $middleware); 356 } else { 357 throw new \InvalidArgumentException('Route middleware must be callable or an array of callables'); 358 } 359 360 return $this; 361 } 362 363 /** 364 * Matches URI? 365 * 366 * Parse this route's pattern, and then compare it to an HTTP resource URI 367 * This method was modeled after the techniques demonstrated by Dan Sosedoff at: 368 * 369 * http://blog.sosedoff.com/2009/09/20/rails-like-php-url-router/ 370 * 371 * @param string $resourceUri A Request URI 372 * @return bool 373 */ 374 public function matches($resourceUri) 375 { 376 //Convert URL params into regex patterns, construct a regex for this route, init params 377 $patternAsRegex = preg_replace_callback( 378 '#:([\w]+)\+?#', 379 array($this, 'matchesCallback'), 380 str_replace(')', ')?', (string)$this->pattern) 381 ); 382 if (substr($this->pattern, -1) === '/') { 383 $patternAsRegex .= '?'; 384 } 385 386 $regex = '#^' . $patternAsRegex . '$#'; 387 388 if ($this->caseSensitive === false) { 389 $regex .= 'i'; 390 } 391 392 //Cache URL params' names and values if this route matches the current HTTP request 393 if (!preg_match($regex, $resourceUri, $paramValues)) { 394 return false; 395 } 396 foreach ($this->paramNames as $name) { 397 if (isset($paramValues[$name])) { 398 if (isset($this->paramNamesPath[$name])) { 399 $this->params[$name] = explode('/', urldecode($paramValues[$name])); 400 } else { 401 $this->params[$name] = urldecode($paramValues[$name]); 402 } 403 } 404 } 405 406 return true; 407 } 408 409 /** 410 * Convert a URL parameter (e.g. ":id", ":id+") into a regular expression 411 * @param array $m URL parameters 412 * @return string Regular expression for URL parameter 413 */ 414 protected function matchesCallback($m) 415 { 416 $this->paramNames[] = $m[1]; 417 if (isset($this->conditions[$m[1]])) { 418 return '(?P<' . $m[1] . '>' . $this->conditions[$m[1]] . ')'; 419 } 420 if (substr($m[0], -1) === '+') { 421 $this->paramNamesPath[$m[1]] = 1; 422 423 return '(?P<' . $m[1] . '>.+)'; 424 } 425 426 return '(?P<' . $m[1] . '>[^/]+)'; 427 } 428 429 /** 430 * Set route name 431 * @param string $name The name of the route 432 * @return \Slim\Route 433 */ 434 public function name($name) 435 { 436 $this->setName($name); 437 438 return $this; 439 } 440 441 /** 442 * Merge route conditions 443 * @param array $conditions Key-value array of URL parameter conditions 444 * @return \Slim\Route 445 */ 446 public function conditions(array $conditions) 447 { 448 $this->conditions = array_merge($this->conditions, $conditions); 449 450 return $this; 451 } 452 453 /** 454 * Dispatch route 455 * 456 * This method invokes the route object's callable. If middleware is 457 * registered for the route, each callable middleware is invoked in 458 * the order specified. 459 * 460 * @return bool 461 */ 462 public function dispatch() 463 { 464 foreach ($this->middleware as $mw) { 465 call_user_func_array($mw, array($this)); 466 } 467 468 $return = call_user_func_array($this->getCallable(), array_values($this->getParams())); 469 return ($return === false) ? false : true; 470 } 471 }