Route.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Routing;
  11. /**
  12. * A Route describes a route and its parameters.
  13. *
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. * @author Tobias Schultze <http://tobion.de>
  16. */
  17. class Route implements \Serializable
  18. {
  19. /**
  20. * @var string
  21. */
  22. private $path = '/';
  23. /**
  24. * @var string
  25. */
  26. private $host = '';
  27. /**
  28. * @var array
  29. */
  30. private $schemes = array();
  31. /**
  32. * @var array
  33. */
  34. private $methods = array();
  35. /**
  36. * @var array
  37. */
  38. private $defaults = array();
  39. /**
  40. * @var array
  41. */
  42. private $requirements = array();
  43. /**
  44. * @var array
  45. */
  46. private $options = array();
  47. /**
  48. * @var null|CompiledRoute
  49. */
  50. private $compiled;
  51. /**
  52. * @var string
  53. */
  54. private $condition = '';
  55. /**
  56. * Constructor.
  57. *
  58. * Available options:
  59. *
  60. * * compiler_class: A class name able to compile this route instance (RouteCompiler by default)
  61. *
  62. * @param string $path The path pattern to match
  63. * @param array $defaults An array of default parameter values
  64. * @param array $requirements An array of requirements for parameters (regexes)
  65. * @param array $options An array of options
  66. * @param string $host The host pattern to match
  67. * @param string|array $schemes A required URI scheme or an array of restricted schemes
  68. * @param string|array $methods A required HTTP method or an array of restricted methods
  69. * @param string $condition A condition that should evaluate to true for the route to match
  70. */
  71. public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '')
  72. {
  73. $this->setPath($path);
  74. $this->setDefaults($defaults);
  75. $this->setRequirements($requirements);
  76. $this->setOptions($options);
  77. $this->setHost($host);
  78. // The conditions make sure that an initial empty $schemes/$methods does not override the corresponding requirement.
  79. // They can be removed when the BC layer is removed.
  80. if ($schemes) {
  81. $this->setSchemes($schemes);
  82. }
  83. if ($methods) {
  84. $this->setMethods($methods);
  85. }
  86. $this->setCondition($condition);
  87. }
  88. /**
  89. * {@inheritdoc}
  90. */
  91. public function serialize()
  92. {
  93. return serialize(array(
  94. 'path' => $this->path,
  95. 'host' => $this->host,
  96. 'defaults' => $this->defaults,
  97. 'requirements' => $this->requirements,
  98. 'options' => $this->options,
  99. 'schemes' => $this->schemes,
  100. 'methods' => $this->methods,
  101. 'condition' => $this->condition,
  102. 'compiled' => $this->compiled,
  103. ));
  104. }
  105. /**
  106. * {@inheritdoc}
  107. */
  108. public function unserialize($serialized)
  109. {
  110. $data = unserialize($serialized);
  111. $this->path = $data['path'];
  112. $this->host = $data['host'];
  113. $this->defaults = $data['defaults'];
  114. $this->requirements = $data['requirements'];
  115. $this->options = $data['options'];
  116. $this->schemes = $data['schemes'];
  117. $this->methods = $data['methods'];
  118. if (isset($data['condition'])) {
  119. $this->condition = $data['condition'];
  120. }
  121. if (isset($data['compiled'])) {
  122. $this->compiled = $data['compiled'];
  123. }
  124. }
  125. /**
  126. * Returns the pattern for the path.
  127. *
  128. * @return string The pattern
  129. *
  130. * @deprecated since version 2.2, to be removed in 3.0. Use getPath instead.
  131. */
  132. public function getPattern()
  133. {
  134. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Use the getPath() method instead.', E_USER_DEPRECATED);
  135. return $this->path;
  136. }
  137. /**
  138. * Sets the pattern for the path.
  139. *
  140. * This method implements a fluent interface.
  141. *
  142. * @param string $pattern The path pattern
  143. *
  144. * @return $this
  145. *
  146. * @deprecated since version 2.2, to be removed in 3.0. Use setPath instead.
  147. */
  148. public function setPattern($pattern)
  149. {
  150. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Use the setPath() method instead.', E_USER_DEPRECATED);
  151. return $this->setPath($pattern);
  152. }
  153. /**
  154. * Returns the pattern for the path.
  155. *
  156. * @return string The path pattern
  157. */
  158. public function getPath()
  159. {
  160. return $this->path;
  161. }
  162. /**
  163. * Sets the pattern for the path.
  164. *
  165. * This method implements a fluent interface.
  166. *
  167. * @param string $pattern The path pattern
  168. *
  169. * @return $this
  170. */
  171. public function setPath($pattern)
  172. {
  173. // A pattern must start with a slash and must not have multiple slashes at the beginning because the
  174. // generated path for this route would be confused with a network path, e.g. '//domain.com/path'.
  175. $this->path = '/'.ltrim(trim($pattern), '/');
  176. $this->compiled = null;
  177. return $this;
  178. }
  179. /**
  180. * Returns the pattern for the host.
  181. *
  182. * @return string The host pattern
  183. */
  184. public function getHost()
  185. {
  186. return $this->host;
  187. }
  188. /**
  189. * Sets the pattern for the host.
  190. *
  191. * This method implements a fluent interface.
  192. *
  193. * @param string $pattern The host pattern
  194. *
  195. * @return $this
  196. */
  197. public function setHost($pattern)
  198. {
  199. $this->host = (string) $pattern;
  200. $this->compiled = null;
  201. return $this;
  202. }
  203. /**
  204. * Returns the lowercased schemes this route is restricted to.
  205. * So an empty array means that any scheme is allowed.
  206. *
  207. * @return array The schemes
  208. */
  209. public function getSchemes()
  210. {
  211. return $this->schemes;
  212. }
  213. /**
  214. * Sets the schemes (e.g. 'https') this route is restricted to.
  215. * So an empty array means that any scheme is allowed.
  216. *
  217. * This method implements a fluent interface.
  218. *
  219. * @param string|array $schemes The scheme or an array of schemes
  220. *
  221. * @return $this
  222. */
  223. public function setSchemes($schemes)
  224. {
  225. $this->schemes = array_map('strtolower', (array) $schemes);
  226. // this is to keep BC and will be removed in a future version
  227. if ($this->schemes) {
  228. $this->requirements['_scheme'] = implode('|', $this->schemes);
  229. } else {
  230. unset($this->requirements['_scheme']);
  231. }
  232. $this->compiled = null;
  233. return $this;
  234. }
  235. /**
  236. * Checks if a scheme requirement has been set.
  237. *
  238. * @param string $scheme
  239. *
  240. * @return bool true if the scheme requirement exists, otherwise false
  241. */
  242. public function hasScheme($scheme)
  243. {
  244. return in_array(strtolower($scheme), $this->schemes, true);
  245. }
  246. /**
  247. * Returns the uppercased HTTP methods this route is restricted to.
  248. * So an empty array means that any method is allowed.
  249. *
  250. * @return array The methods
  251. */
  252. public function getMethods()
  253. {
  254. return $this->methods;
  255. }
  256. /**
  257. * Sets the HTTP methods (e.g. 'POST') this route is restricted to.
  258. * So an empty array means that any method is allowed.
  259. *
  260. * This method implements a fluent interface.
  261. *
  262. * @param string|array $methods The method or an array of methods
  263. *
  264. * @return $this
  265. */
  266. public function setMethods($methods)
  267. {
  268. $this->methods = array_map('strtoupper', (array) $methods);
  269. // this is to keep BC and will be removed in a future version
  270. if ($this->methods) {
  271. $this->requirements['_method'] = implode('|', $this->methods);
  272. } else {
  273. unset($this->requirements['_method']);
  274. }
  275. $this->compiled = null;
  276. return $this;
  277. }
  278. /**
  279. * Returns the options.
  280. *
  281. * @return array The options
  282. */
  283. public function getOptions()
  284. {
  285. return $this->options;
  286. }
  287. /**
  288. * Sets the options.
  289. *
  290. * This method implements a fluent interface.
  291. *
  292. * @param array $options The options
  293. *
  294. * @return $this
  295. */
  296. public function setOptions(array $options)
  297. {
  298. $this->options = array(
  299. 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',
  300. );
  301. return $this->addOptions($options);
  302. }
  303. /**
  304. * Adds options.
  305. *
  306. * This method implements a fluent interface.
  307. *
  308. * @param array $options The options
  309. *
  310. * @return $this
  311. */
  312. public function addOptions(array $options)
  313. {
  314. foreach ($options as $name => $option) {
  315. $this->options[$name] = $option;
  316. }
  317. $this->compiled = null;
  318. return $this;
  319. }
  320. /**
  321. * Sets an option value.
  322. *
  323. * This method implements a fluent interface.
  324. *
  325. * @param string $name An option name
  326. * @param mixed $value The option value
  327. *
  328. * @return $this
  329. */
  330. public function setOption($name, $value)
  331. {
  332. $this->options[$name] = $value;
  333. $this->compiled = null;
  334. return $this;
  335. }
  336. /**
  337. * Get an option value.
  338. *
  339. * @param string $name An option name
  340. *
  341. * @return mixed The option value or null when not given
  342. */
  343. public function getOption($name)
  344. {
  345. return isset($this->options[$name]) ? $this->options[$name] : null;
  346. }
  347. /**
  348. * Checks if an option has been set.
  349. *
  350. * @param string $name An option name
  351. *
  352. * @return bool true if the option is set, false otherwise
  353. */
  354. public function hasOption($name)
  355. {
  356. return array_key_exists($name, $this->options);
  357. }
  358. /**
  359. * Returns the defaults.
  360. *
  361. * @return array The defaults
  362. */
  363. public function getDefaults()
  364. {
  365. return $this->defaults;
  366. }
  367. /**
  368. * Sets the defaults.
  369. *
  370. * This method implements a fluent interface.
  371. *
  372. * @param array $defaults The defaults
  373. *
  374. * @return $this
  375. */
  376. public function setDefaults(array $defaults)
  377. {
  378. $this->defaults = array();
  379. return $this->addDefaults($defaults);
  380. }
  381. /**
  382. * Adds defaults.
  383. *
  384. * This method implements a fluent interface.
  385. *
  386. * @param array $defaults The defaults
  387. *
  388. * @return $this
  389. */
  390. public function addDefaults(array $defaults)
  391. {
  392. foreach ($defaults as $name => $default) {
  393. $this->defaults[$name] = $default;
  394. }
  395. $this->compiled = null;
  396. return $this;
  397. }
  398. /**
  399. * Gets a default value.
  400. *
  401. * @param string $name A variable name
  402. *
  403. * @return mixed The default value or null when not given
  404. */
  405. public function getDefault($name)
  406. {
  407. return isset($this->defaults[$name]) ? $this->defaults[$name] : null;
  408. }
  409. /**
  410. * Checks if a default value is set for the given variable.
  411. *
  412. * @param string $name A variable name
  413. *
  414. * @return bool true if the default value is set, false otherwise
  415. */
  416. public function hasDefault($name)
  417. {
  418. return array_key_exists($name, $this->defaults);
  419. }
  420. /**
  421. * Sets a default value.
  422. *
  423. * @param string $name A variable name
  424. * @param mixed $default The default value
  425. *
  426. * @return $this
  427. */
  428. public function setDefault($name, $default)
  429. {
  430. $this->defaults[$name] = $default;
  431. $this->compiled = null;
  432. return $this;
  433. }
  434. /**
  435. * Returns the requirements.
  436. *
  437. * @return array The requirements
  438. */
  439. public function getRequirements()
  440. {
  441. return $this->requirements;
  442. }
  443. /**
  444. * Sets the requirements.
  445. *
  446. * This method implements a fluent interface.
  447. *
  448. * @param array $requirements The requirements
  449. *
  450. * @return $this
  451. */
  452. public function setRequirements(array $requirements)
  453. {
  454. $this->requirements = array();
  455. return $this->addRequirements($requirements);
  456. }
  457. /**
  458. * Adds requirements.
  459. *
  460. * This method implements a fluent interface.
  461. *
  462. * @param array $requirements The requirements
  463. *
  464. * @return $this
  465. */
  466. public function addRequirements(array $requirements)
  467. {
  468. foreach ($requirements as $key => $regex) {
  469. $this->requirements[$key] = $this->sanitizeRequirement($key, $regex);
  470. }
  471. $this->compiled = null;
  472. return $this;
  473. }
  474. /**
  475. * Returns the requirement for the given key.
  476. *
  477. * @param string $key The key
  478. *
  479. * @return string|null The regex or null when not given
  480. */
  481. public function getRequirement($key)
  482. {
  483. if ('_scheme' === $key) {
  484. @trigger_error('The "_scheme" requirement is deprecated since version 2.2 and will be removed in 3.0. Use getSchemes() instead.', E_USER_DEPRECATED);
  485. } elseif ('_method' === $key) {
  486. @trigger_error('The "_method" requirement is deprecated since version 2.2 and will be removed in 3.0. Use getMethods() instead.', E_USER_DEPRECATED);
  487. }
  488. return isset($this->requirements[$key]) ? $this->requirements[$key] : null;
  489. }
  490. /**
  491. * Checks if a requirement is set for the given key.
  492. *
  493. * @param string $key A variable name
  494. *
  495. * @return bool true if a requirement is specified, false otherwise
  496. */
  497. public function hasRequirement($key)
  498. {
  499. return array_key_exists($key, $this->requirements);
  500. }
  501. /**
  502. * Sets a requirement for the given key.
  503. *
  504. * @param string $key The key
  505. * @param string $regex The regex
  506. *
  507. * @return $this
  508. */
  509. public function setRequirement($key, $regex)
  510. {
  511. $this->requirements[$key] = $this->sanitizeRequirement($key, $regex);
  512. $this->compiled = null;
  513. return $this;
  514. }
  515. /**
  516. * Returns the condition.
  517. *
  518. * @return string The condition
  519. */
  520. public function getCondition()
  521. {
  522. return $this->condition;
  523. }
  524. /**
  525. * Sets the condition.
  526. *
  527. * This method implements a fluent interface.
  528. *
  529. * @param string $condition The condition
  530. *
  531. * @return $this
  532. */
  533. public function setCondition($condition)
  534. {
  535. $this->condition = (string) $condition;
  536. $this->compiled = null;
  537. return $this;
  538. }
  539. /**
  540. * Compiles the route.
  541. *
  542. * @return CompiledRoute A CompiledRoute instance
  543. *
  544. * @throws \LogicException If the Route cannot be compiled because the
  545. * path or host pattern is invalid
  546. *
  547. * @see RouteCompiler which is responsible for the compilation process
  548. */
  549. public function compile()
  550. {
  551. if (null !== $this->compiled) {
  552. return $this->compiled;
  553. }
  554. $class = $this->getOption('compiler_class');
  555. return $this->compiled = $class::compile($this);
  556. }
  557. private function sanitizeRequirement($key, $regex)
  558. {
  559. if (!is_string($regex)) {
  560. throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key));
  561. }
  562. if ('' !== $regex && '^' === $regex[0]) {
  563. $regex = (string) substr($regex, 1); // returns false for a single character
  564. }
  565. if ('$' === substr($regex, -1)) {
  566. $regex = substr($regex, 0, -1);
  567. }
  568. if ('' === $regex) {
  569. throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key));
  570. }
  571. // this is to keep BC and will be removed in a future version
  572. if ('_scheme' === $key) {
  573. @trigger_error('The "_scheme" requirement is deprecated since version 2.2 and will be removed in 3.0. Use the setSchemes() method instead.', E_USER_DEPRECATED);
  574. $this->setSchemes(explode('|', $regex));
  575. } elseif ('_method' === $key) {
  576. @trigger_error('The "_method" requirement is deprecated since version 2.2 and will be removed in 3.0. Use the setMethods() method instead.', E_USER_DEPRECATED);
  577. $this->setMethods(explode('|', $regex));
  578. }
  579. return $regex;
  580. }
  581. }