Route.php 16 KB

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