Route.php 14 KB

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