CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.2 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.2
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • CakeRoute
  • PluginShortRoute
  • RedirectRoute
  1: <?php
  2: /**
  3:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4:  * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  5:  *
  6:  * Licensed under The MIT License
  7:  * Redistributions of files must retain the above copyright notice.
  8:  *
  9:  * @copyright     Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
 10:  * @link          http://cakephp.org CakePHP(tm) Project
 11:  * @since         CakePHP(tm) v 1.3
 12:  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 13:  */
 14: App::uses('Hash', 'Utility');
 15: 
 16: /**
 17:  * A single Route used by the Router to connect requests to
 18:  * parameter maps.
 19:  *
 20:  * Not normally created as a standalone.  Use Router::connect() to create
 21:  * Routes for your application.
 22:  *
 23:  * @package Cake.Routing.Route
 24:  */
 25: class CakeRoute {
 26: 
 27: /**
 28:  * An array of named segments in a Route.
 29:  * `/:controller/:action/:id` has 3 key elements
 30:  *
 31:  * @var array
 32:  */
 33:     public $keys = array();
 34: 
 35: /**
 36:  * An array of additional parameters for the Route.
 37:  *
 38:  * @var array
 39:  */
 40:     public $options = array();
 41: 
 42: /**
 43:  * Default parameters for a Route
 44:  *
 45:  * @var array
 46:  */
 47:     public $defaults = array();
 48: 
 49: /**
 50:  * The routes template string.
 51:  *
 52:  * @var string
 53:  */
 54:     public $template = null;
 55: 
 56: /**
 57:  * Is this route a greedy route?  Greedy routes have a `/*` in their
 58:  * template
 59:  *
 60:  * @var string
 61:  */
 62:     protected $_greedy = false;
 63: 
 64: /**
 65:  * The compiled route regular expression
 66:  *
 67:  * @var string
 68:  */
 69:     protected $_compiledRoute = null;
 70: 
 71: /**
 72:  * HTTP header shortcut map.  Used for evaluating header-based route expressions.
 73:  *
 74:  * @var array
 75:  */
 76:     protected $_headerMap = array(
 77:         'type' => 'content_type',
 78:         'method' => 'request_method',
 79:         'server' => 'server_name'
 80:     );
 81: 
 82: /**
 83:  * Constructor for a Route
 84:  *
 85:  * @param string $template Template string with parameter placeholders
 86:  * @param array $defaults Array of defaults for the route.
 87:  * @param array $options Array of additional options for the Route
 88:  */
 89:     public function __construct($template, $defaults = array(), $options = array()) {
 90:         $this->template = $template;
 91:         $this->defaults = (array)$defaults;
 92:         $this->options = (array)$options;
 93:     }
 94: 
 95: /**
 96:  * Check if a Route has been compiled into a regular expression.
 97:  *
 98:  * @return boolean
 99:  */
100:     public function compiled() {
101:         return !empty($this->_compiledRoute);
102:     }
103: 
104: /**
105:  * Compiles the route's regular expression.  Modifies defaults property so all necessary keys are set
106:  * and populates $this->names with the named routing elements.
107:  *
108:  * @return array Returns a string regular expression of the compiled route.
109:  */
110:     public function compile() {
111:         if ($this->compiled()) {
112:             return $this->_compiledRoute;
113:         }
114:         $this->_writeRoute();
115:         return $this->_compiledRoute;
116:     }
117: 
118: /**
119:  * Builds a route regular expression.  Uses the template, defaults and options
120:  * properties to compile a regular expression that can be used to parse request strings.
121:  *
122:  * @return void
123:  */
124:     protected function _writeRoute() {
125:         if (empty($this->template) || ($this->template === '/')) {
126:             $this->_compiledRoute = '#^/*$#';
127:             $this->keys = array();
128:             return;
129:         }
130:         $route = $this->template;
131:         $names = $routeParams = array();
132:         $parsed = preg_quote($this->template, '#');
133: 
134:         preg_match_all('#:([A-Za-z0-9_-]+[A-Z0-9a-z])#', $route, $namedElements);
135:         foreach ($namedElements[1] as $i => $name) {
136:             $search = '\\' . $namedElements[0][$i];
137:             if (isset($this->options[$name])) {
138:                 $option = null;
139:                 if ($name !== 'plugin' && array_key_exists($name, $this->defaults)) {
140:                     $option = '?';
141:                 }
142:                 $slashParam = '/\\' . $namedElements[0][$i];
143:                 if (strpos($parsed, $slashParam) !== false) {
144:                     $routeParams[$slashParam] = '(?:/(?P<' . $name . '>' . $this->options[$name] . ')' . $option . ')' . $option;
145:                 } else {
146:                     $routeParams[$search] = '(?:(?P<' . $name . '>' . $this->options[$name] . ')' . $option . ')' . $option;
147:                 }
148:             } else {
149:                 $routeParams[$search] = '(?:(?P<' . $name . '>[^/]+))';
150:             }
151:             $names[] = $name;
152:         }
153:         if (preg_match('#\/\*\*$#', $route)) {
154:             $parsed = preg_replace('#/\\\\\*\\\\\*$#', '(?:/(?P<_trailing_>.*))?', $parsed);
155:             $this->_greedy = true;
156:         } elseif (preg_match('#\/\*$#', $route)) {
157:             $parsed = preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed);
158:             $this->_greedy = true;
159:         }
160:         krsort($routeParams);
161:         $parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed);
162:         $this->_compiledRoute = '#^' . $parsed . '[/]*$#';
163:         $this->keys = $names;
164: 
165:         //remove defaults that are also keys. They can cause match failures
166:         foreach ($this->keys as $key) {
167:             unset($this->defaults[$key]);
168:         }
169:     }
170: 
171: /**
172:  * Checks to see if the given URL can be parsed by this route.
173:  * If the route can be parsed an array of parameters will be returned; if not
174:  * false will be returned. String urls are parsed if they match a routes regular expression.
175:  *
176:  * @param string $url The url to attempt to parse.
177:  * @return mixed Boolean false on failure, otherwise an array or parameters
178:  */
179:     public function parse($url) {
180:         if (!$this->compiled()) {
181:             $this->compile();
182:         }
183:         if (!preg_match($this->_compiledRoute, $url, $route)) {
184:             return false;
185:         }
186:         foreach ($this->defaults as $key => $val) {
187:             $key = (string)$key;
188:             if ($key[0] === '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) {
189:                 if (isset($this->_headerMap[$header[1]])) {
190:                     $header = $this->_headerMap[$header[1]];
191:                 } else {
192:                     $header = 'http_' . $header[1];
193:                 }
194:                 $header = strtoupper($header);
195: 
196:                 $val = (array)$val;
197:                 $h = false;
198: 
199:                 foreach ($val as $v) {
200:                     if (env($header) === $v) {
201:                         $h = true;
202:                     }
203:                 }
204:                 if (!$h) {
205:                     return false;
206:                 }
207:             }
208:         }
209:         array_shift($route);
210:         $count = count($this->keys);
211:         for ($i = 0; $i <= $count; $i++) {
212:             unset($route[$i]);
213:         }
214:         $route['pass'] = $route['named'] = array();
215: 
216:         // Assign defaults, set passed args to pass
217:         foreach ($this->defaults as $key => $value) {
218:             if (isset($route[$key])) {
219:                 continue;
220:             }
221:             if (is_integer($key)) {
222:                 $route['pass'][] = $value;
223:                 continue;
224:             }
225:             $route[$key] = $value;
226:         }
227: 
228:         foreach ($this->keys as $key) {
229:             if (isset($route[$key])) {
230:                 $route[$key] = rawurldecode($route[$key]);
231:             }
232:         }
233: 
234:         if (isset($route['_args_'])) {
235:             list($pass, $named) = $this->_parseArgs($route['_args_'], $route);
236:             $route['pass'] = array_merge($route['pass'], $pass);
237:             $route['named'] = $named;
238:             unset($route['_args_']);
239:         }
240: 
241:         if (isset($route['_trailing_'])) {
242:             $route['pass'][] = rawurldecode($route['_trailing_']);
243:             unset($route['_trailing_']);
244:         }
245: 
246:         // restructure 'pass' key route params
247:         if (isset($this->options['pass'])) {
248:             $j = count($this->options['pass']);
249:             while ($j--) {
250:                 if (isset($route[$this->options['pass'][$j]])) {
251:                     array_unshift($route['pass'], $route[$this->options['pass'][$j]]);
252:                 }
253:             }
254:         }
255:         return $route;
256:     }
257: 
258: /**
259:  * Parse passed and Named parameters into a list of passed args, and a hash of named parameters.
260:  * The local and global configuration for named parameters will be used.
261:  *
262:  * @param string $args A string with the passed & named params.  eg. /1/page:2
263:  * @param string $context The current route context, which should contain controller/action keys.
264:  * @return array Array of ($pass, $named)
265:  */
266:     protected function _parseArgs($args, $context) {
267:         $pass = $named = array();
268:         $args = explode('/', $args);
269: 
270:         $namedConfig = Router::namedConfig();
271:         $greedy = $namedConfig['greedyNamed'];
272:         $rules = $namedConfig['rules'];
273:         if (!empty($this->options['named'])) {
274:             $greedy = isset($this->options['greedyNamed']) && $this->options['greedyNamed'] === true;
275:             foreach ((array)$this->options['named'] as $key => $val) {
276:                 if (is_numeric($key)) {
277:                     $rules[$val] = true;
278:                     continue;
279:                 }
280:                 $rules[$key] = $val;
281:             }
282:         }
283: 
284:         foreach ($args as $param) {
285:             if (empty($param) && $param !== '0' && $param !== 0) {
286:                 continue;
287:             }
288: 
289:             $separatorIsPresent = strpos($param, $namedConfig['separator']) !== false;
290:             if ((!isset($this->options['named']) || !empty($this->options['named'])) && $separatorIsPresent) {
291:                 list($key, $val) = explode($namedConfig['separator'], $param, 2);
292:                 $key = rawurldecode($key);
293:                 $val = rawurldecode($val);
294:                 $hasRule = isset($rules[$key]);
295:                 $passIt = (!$hasRule && !$greedy) || ($hasRule && !$this->_matchNamed($val, $rules[$key], $context));
296:                 if ($passIt) {
297:                     $pass[] = rawurldecode($param);
298:                 } else {
299:                     if (preg_match_all('/\[([A-Za-z0-9_-]+)?\]/', $key, $matches, PREG_SET_ORDER)) {
300:                         $matches = array_reverse($matches);
301:                         $parts = explode('[', $key);
302:                         $key = array_shift($parts);
303:                         $arr = $val;
304:                         foreach ($matches as $match) {
305:                             if (empty($match[1])) {
306:                                 $arr = array($arr);
307:                             } else {
308:                                 $arr = array(
309:                                     $match[1] => $arr
310:                                 );
311:                             }
312:                         }
313:                         $val = $arr;
314:                     }
315:                     $named = array_merge_recursive($named, array($key => $val));
316:                 }
317:             } else {
318:                 $pass[] = rawurldecode($param);
319:             }
320:         }
321:         return array($pass, $named);
322:     }
323: 
324: /**
325:  * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented
326:  * rule types are controller, action and match that can be combined with each other.
327:  *
328:  * @param string $val The value of the named parameter
329:  * @param array $rule The rule(s) to apply, can also be a match string
330:  * @param string $context An array with additional context information (controller / action)
331:  * @return boolean
332:  */
333:     protected function _matchNamed($val, $rule, $context) {
334:         if ($rule === true || $rule === false) {
335:             return $rule;
336:         }
337:         if (is_string($rule)) {
338:             $rule = array('match' => $rule);
339:         }
340:         if (!is_array($rule)) {
341:             return false;
342:         }
343: 
344:         $controllerMatches = (
345:             !isset($rule['controller'], $context['controller']) ||
346:             in_array($context['controller'], (array)$rule['controller'])
347:         );
348:         if (!$controllerMatches) {
349:             return false;
350:         }
351:         $actionMatches = (
352:             !isset($rule['action'], $context['action']) ||
353:             in_array($context['action'], (array)$rule['action'])
354:         );
355:         if (!$actionMatches) {
356:             return false;
357:         }
358:         return (!isset($rule['match']) || preg_match('/' . $rule['match'] . '/', $val));
359:     }
360: 
361: /**
362:  * Apply persistent parameters to a url array. Persistent parameters are a special
363:  * key used during route creation to force route parameters to persist when omitted from
364:  * a url array.
365:  *
366:  * @param array $url The array to apply persistent parameters to.
367:  * @param array $params An array of persistent values to replace persistent ones.
368:  * @return array An array with persistent parameters applied.
369:  */
370:     public function persistParams($url, $params) {
371:         foreach ($this->options['persist'] as $persistKey) {
372:             if (array_key_exists($persistKey, $params) && !isset($url[$persistKey])) {
373:                 $url[$persistKey] = $params[$persistKey];
374:             }
375:         }
376:         return $url;
377:     }
378: 
379: /**
380:  * Attempt to match a url array.  If the url matches the route parameters and settings, then
381:  * return a generated string url.  If the url doesn't match the route parameters, false will be returned.
382:  * This method handles the reverse routing or conversion of url arrays into string urls.
383:  *
384:  * @param array $url An array of parameters to check matching with.
385:  * @return mixed Either a string url for the parameters if they match or false.
386:  */
387:     public function match($url) {
388:         if (!$this->compiled()) {
389:             $this->compile();
390:         }
391:         $defaults = $this->defaults;
392: 
393:         if (isset($defaults['prefix'])) {
394:             $url['prefix'] = $defaults['prefix'];
395:         }
396: 
397:         //check that all the key names are in the url
398:         $keyNames = array_flip($this->keys);
399:         if (array_intersect_key($keyNames, $url) !== $keyNames) {
400:             return false;
401:         }
402: 
403:         // Missing defaults is a fail.
404:         if (array_diff_key($defaults, $url) !== array()) {
405:             return false;
406:         }
407: 
408:         $namedConfig = Router::namedConfig();
409:         $prefixes = Router::prefixes();
410:         $greedyNamed = $namedConfig['greedyNamed'];
411:         $allowedNamedParams = $namedConfig['rules'];
412: 
413:         $named = $pass = array();
414: 
415:         foreach ($url as $key => $value) {
416: 
417:             // keys that exist in the defaults and have different values is a match failure.
418:             $defaultExists = array_key_exists($key, $defaults);
419:             if ($defaultExists && $defaults[$key] != $value) {
420:                 return false;
421:             } elseif ($defaultExists) {
422:                 continue;
423:             }
424: 
425:             // If the key is a routed key, its not different yet.
426:             if (array_key_exists($key, $keyNames)) {
427:                 continue;
428:             }
429: 
430:             // pull out passed args
431:             $numeric = is_numeric($key);
432:             if ($numeric && isset($defaults[$key]) && $defaults[$key] == $value) {
433:                 continue;
434:             } elseif ($numeric) {
435:                 $pass[] = $value;
436:                 unset($url[$key]);
437:                 continue;
438:             }
439: 
440:             // pull out named params if named params are greedy or a rule exists.
441:             if (
442:                 ($greedyNamed || isset($allowedNamedParams[$key])) &&
443:                 ($value !== false && $value !== null) &&
444:                 (!in_array($key, $prefixes))
445:             ) {
446:                 $named[$key] = $value;
447:                 continue;
448:             }
449: 
450:             // keys that don't exist are different.
451:             if (!$defaultExists && !empty($value)) {
452:                 return false;
453:             }
454:         }
455: 
456:         //if a not a greedy route, no extra params are allowed.
457:         if (!$this->_greedy && (!empty($pass) || !empty($named))) {
458:             return false;
459:         }
460: 
461:         //check patterns for routed params
462:         if (!empty($this->options)) {
463:             foreach ($this->options as $key => $pattern) {
464:                 if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) {
465:                     return false;
466:                 }
467:             }
468:         }
469:         return $this->_writeUrl(array_merge($url, compact('pass', 'named')));
470:     }
471: 
472: /**
473:  * Converts a matching route array into a url string. Composes the string url using the template
474:  * used to create the route.
475:  *
476:  * @param array $params The params to convert to a string url.
477:  * @return string Composed route string.
478:  */
479:     protected function _writeUrl($params) {
480:         if (isset($params['prefix'])) {
481:             $prefixed = $params['prefix'] . '_';
482:         }
483:         if (isset($prefixed, $params['action']) && strpos($params['action'], $prefixed) === 0) {
484:             $params['action'] = substr($params['action'], strlen($prefixed) * -1);
485:             unset($params['prefix']);
486:         }
487: 
488:         if (is_array($params['pass'])) {
489:             $params['pass'] = implode('/', array_map('rawurlencode', $params['pass']));
490:         }
491: 
492:         $namedConfig = Router::namedConfig();
493:         $separator = $namedConfig['separator'];
494: 
495:         if (!empty($params['named']) && is_array($params['named'])) {
496:             $named = array();
497:             foreach ($params['named'] as $key => $value) {
498:                 if (is_array($value)) {
499:                     $flat = Hash::flatten($value, '%5D%5B');
500:                     foreach ($flat as $namedKey => $namedValue) {
501:                         $named[] = $key . "%5B{$namedKey}%5D" . $separator . rawurlencode($namedValue);
502:                     }
503:                 } else {
504:                     $named[] = $key . $separator . rawurlencode($value);
505:                 }
506:             }
507:             $params['pass'] = $params['pass'] . '/' . implode('/', $named);
508:         }
509:         $out = $this->template;
510: 
511:         $search = $replace = array();
512:         foreach ($this->keys as $key) {
513:             $string = null;
514:             if (isset($params[$key])) {
515:                 $string = $params[$key];
516:             } elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
517:                 $key .= '/';
518:             }
519:             $search[] = ':' . $key;
520:             $replace[] = $string;
521:         }
522:         $out = str_replace($search, $replace, $out);
523: 
524:         if (strpos($this->template, '*')) {
525:             $out = str_replace('*', $params['pass'], $out);
526:         }
527:         $out = str_replace('//', '/', $out);
528:         return $out;
529:     }
530: 
531: }
532: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs