PageRenderTime 57ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Classes/TYPO3/FLOW3/Mvc/Routing/Route.php

https://github.com/christianjul/FLOW3-Composer
PHP | 622 lines | 333 code | 49 blank | 240 comment | 70 complexity | 051a811f73e516703720cbd39a41b8f6 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. namespace TYPO3\FLOW3\Mvc\Routing;
  3. /* *
  4. * This script belongs to the FLOW3 framework. *
  5. * *
  6. * It is free software; you can redistribute it and/or modify it under *
  7. * the terms of the GNU Lesser General Public License, either version 3 *
  8. * of the License, or (at your option) any later version. *
  9. * *
  10. * The TYPO3 project - inspiring people to share! *
  11. * */
  12. use TYPO3\FLOW3\Annotations as FLOW3;
  13. /**
  14. * Implementation of a standard route
  15. */
  16. class Route {
  17. const ROUTEPART_TYPE_STATIC = 'static';
  18. const ROUTEPART_TYPE_DYNAMIC = 'dynamic';
  19. const PATTERN_EXTRACTROUTEPARTS = '/(?P<optionalStart>\(?)(?P<dynamic>{?)(?P<content>@?[^}{\(\)]+)}?(?P<optionalEnd>\)?)/';
  20. /**
  21. * Route name
  22. *
  23. * @var string
  24. */
  25. protected $name = NULL;
  26. /**
  27. * Default values
  28. *
  29. * @var array
  30. */
  31. protected $defaults = array();
  32. /**
  33. * URI Pattern of this route
  34. *
  35. * @var string
  36. */
  37. protected $uriPattern = NULL;
  38. /**
  39. * Specifies whether Route Parts of this Route should be converted to lower case when resolved.
  40. *
  41. * @var boolean
  42. */
  43. protected $lowerCase = TRUE;
  44. /**
  45. * Specifies whether Route Values, that are not part of the Routes configuration, should be appended as query string
  46. *
  47. * @var boolean
  48. */
  49. protected $appendExceedingArguments = FALSE;
  50. /**
  51. * Contains the routing results (indexed by "package", "controller" and
  52. * "action") after a successful call of matches()
  53. *
  54. * @var array
  55. */
  56. protected $matchResults = array();
  57. /**
  58. * Contains the matching uri (excluding protocol and host) after a
  59. * successful call of resolves()
  60. *
  61. * @var string
  62. */
  63. protected $matchingUri;
  64. /**
  65. * Contains associative array of Route Part options
  66. * (key: Route Part name, value: array of Route Part options)
  67. *
  68. * @var array
  69. */
  70. protected $routePartsConfiguration = array();
  71. /**
  72. * Indicates whether this route is parsed.
  73. * For better performance, routes are only parsed if needed.
  74. *
  75. * @var boolean
  76. */
  77. protected $isParsed = FALSE;
  78. /**
  79. * Container for Route Parts.
  80. *
  81. * @var array
  82. */
  83. protected $routeParts = array();
  84. /**
  85. * @var \TYPO3\FLOW3\Object\ObjectManagerInterface
  86. * @FLOW3\Inject
  87. */
  88. protected $objectManager;
  89. /**
  90. * @var \TYPO3\FLOW3\Persistence\PersistenceManagerInterface
  91. */
  92. protected $persistenceManager;
  93. /**
  94. * @var \TYPO3\FLOW3\Mvc\Routing\RouterInterface
  95. */
  96. protected $router;
  97. /**
  98. * Injects the Persistence Manager
  99. *
  100. * @param \TYPO3\FLOW3\Persistence\PersistenceManagerInterface $persistenceManager
  101. * @return void
  102. */
  103. public function injectPersistenceManager(\TYPO3\FLOW3\Persistence\PersistenceManagerInterface $persistenceManager) {
  104. $this->persistenceManager = $persistenceManager;
  105. }
  106. /**
  107. * @param \TYPO3\FLOW3\Mvc\Routing\RouterInterface $router
  108. * @return void
  109. */
  110. public function injectRouter(\TYPO3\FLOW3\Mvc\Routing\RouterInterface $router) {
  111. $this->router = $router;
  112. }
  113. /**
  114. * Sets Route name.
  115. *
  116. * @param string $name The Route name
  117. * @return void
  118. */
  119. public function setName($name) {
  120. $this->name = $name;
  121. }
  122. /**
  123. * Returns the name of this Route.
  124. *
  125. * @return string Route name.
  126. */
  127. public function getName() {
  128. return $this->name;
  129. }
  130. /**
  131. * Sets default values for this Route.
  132. * This array is merged with the actual matchResults when match() is called.
  133. *
  134. * @param array $defaults
  135. * @return void
  136. */
  137. public function setDefaults(array $defaults) {
  138. $this->defaults = $defaults;
  139. }
  140. /**
  141. * Returns default values for this Route.
  142. *
  143. * @return array Route defaults
  144. */
  145. public function getDefaults() {
  146. return $this->defaults;
  147. }
  148. /**
  149. * Sets the URI pattern this route should match with
  150. *
  151. * @param string $uriPattern
  152. * @return void
  153. * @throws \InvalidArgumentException
  154. */
  155. public function setUriPattern($uriPattern) {
  156. if (!is_string($uriPattern)) throw new \InvalidArgumentException('URI Pattern must be of type string, ' . gettype($uriPattern) . ' given.', 1223499724);
  157. $this->uriPattern = $uriPattern;
  158. $this->isParsed = FALSE;
  159. }
  160. /**
  161. * Returns the URI pattern this route should match with
  162. *
  163. * @return string the URI pattern
  164. */
  165. public function getUriPattern() {
  166. return $this->uriPattern;
  167. }
  168. /**
  169. * Specifies whether Route parts of this route should be converted to lower case when resolved.
  170. * This setting can be overwritten for all dynamic Route parts.
  171. *
  172. * @param boolean $lowerCase TRUE: Route parts are converted to lower case by default. FALSE: Route parts are not altered.
  173. * @return void
  174. */
  175. public function setLowerCase($lowerCase) {
  176. $this->lowerCase = (boolean)$lowerCase;
  177. }
  178. /**
  179. * Getter for $this->lowerCase.
  180. *
  181. * @return boolean TRUE if this Route part will be converted to lower case, otherwise FALSE.
  182. * @see setLowerCase()
  183. */
  184. public function isLowerCase() {
  185. return $this->lowerCase;
  186. }
  187. /**
  188. * Specifies whether Route values, that are not part of the Route configuration, should be appended to the
  189. * Resulting URI as query string.
  190. * If set to FALSE, the route won't resolve if there are route values left after iterating through all Route Part
  191. * handlers and removing the matching default values.
  192. *
  193. * @param boolean $appendExceedingArguments TRUE: exceeding arguments will be appended to the resulting URI
  194. * @return void
  195. */
  196. public function setAppendExceedingArguments($appendExceedingArguments) {
  197. $this->appendExceedingArguments = (boolean)$appendExceedingArguments;
  198. }
  199. /**
  200. * Returns TRUE if exceeding arguments should be appended to the URI as query string, otherwise FALSE
  201. *
  202. * @return boolean
  203. */
  204. public function getAppendExceedingArguments() {
  205. return $this->appendExceedingArguments;
  206. }
  207. /**
  208. * By default all Dynamic Route Parts are resolved by
  209. * \TYPO3\FLOW3\Mvc\Routing\DynamicRoutePart.
  210. * But you can specify different classes to handle particular Route Parts.
  211. *
  212. * Note: Route Part handlers must implement
  213. * \TYPO3\FLOW3\Mvc\Routing\DynamicRoutePartInterface.
  214. *
  215. * Usage: setRoutePartsConfiguration(array('@controller' =>
  216. * array('handler' => 'TYPO3\Package\Subpackage\MyRoutePartHandler')));
  217. *
  218. * @param array $routePartsConfiguration Route Parts configuration options
  219. * @return void
  220. */
  221. public function setRoutePartsConfiguration(array $routePartsConfiguration) {
  222. $this->routePartsConfiguration = $routePartsConfiguration;
  223. }
  224. /**
  225. * Returns the route parts configuration of this route
  226. *
  227. * @return array $routePartsConfiguration
  228. */
  229. public function getRoutePartsConfiguration() {
  230. return $this->routePartsConfiguration;
  231. }
  232. /**
  233. * Returns an array with the Route match results.
  234. *
  235. * @return array An array of Route Parts and their values for further handling by the Router
  236. * @see \TYPO3\FLOW3\Mvc\Routing\Router
  237. */
  238. public function getMatchResults() {
  239. return $this->matchResults;
  240. }
  241. /**
  242. * Returns the uri which corresponds to this Route.
  243. *
  244. * @return string A string containing the corresponding uri (excluding protocol and host)
  245. */
  246. public function getMatchingUri() {
  247. return $this->matchingUri;
  248. }
  249. /**
  250. * Checks whether $routePath corresponds to this Route.
  251. * If all Route Parts match successfully TRUE is returned and
  252. * $this->matchResults contains an array combining Route default values and
  253. * calculated matchResults from the individual Route Parts.
  254. *
  255. * @param string $routePath the route path without protocol, host and query string
  256. * @return boolean TRUE if this Route corresponds to the given $routePath, otherwise FALSE
  257. * @throws \TYPO3\FLOW3\Mvc\Exception\InvalidRoutePartValueException
  258. * @see getMatchResults()
  259. */
  260. public function matches($routePath) {
  261. $this->matchResults = NULL;
  262. if ($routePath === NULL) {
  263. return FALSE;
  264. }
  265. if ($this->uriPattern === NULL) {
  266. return FALSE;
  267. }
  268. if (!$this->isParsed) {
  269. $this->parse();
  270. }
  271. $matchResults = array();
  272. $routePath = trim($routePath, '/');
  273. $skipOptionalParts = FALSE;
  274. $optionalPartCount = 0;
  275. foreach ($this->routeParts as $routePart) {
  276. if ($routePart->isOptional()) {
  277. $optionalPartCount++;
  278. if ($skipOptionalParts) {
  279. if ($routePart->getDefaultValue() === NULL) {
  280. return FALSE;
  281. }
  282. continue;
  283. }
  284. } else {
  285. $optionalPartCount = 0;
  286. $skipOptionalParts = FALSE;
  287. }
  288. if ($routePart->match($routePath) !== TRUE) {
  289. if ($routePart->isOptional() && $optionalPartCount === 1) {
  290. if ($routePart->getDefaultValue() === NULL) {
  291. return FALSE;
  292. }
  293. $skipOptionalParts = TRUE;
  294. } else {
  295. return FALSE;
  296. }
  297. }
  298. $routePartValue = $routePart->getValue();
  299. if ($routePartValue !== NULL) {
  300. if ($this->containsObject($routePartValue)) {
  301. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidRoutePartValueException('RoutePart::getValue() must only return simple types after calling RoutePart::match(). RoutePart "' . get_class($routePart) . '" returned one or more objects in Route "' . $this->getName() . '".');
  302. }
  303. $matchResults = \TYPO3\FLOW3\Utility\Arrays::setValueByPath($matchResults, $routePart->getName(), $routePartValue);
  304. }
  305. }
  306. if (strlen($routePath) > 0) {
  307. return FALSE;
  308. }
  309. $this->matchResults = \TYPO3\FLOW3\Utility\Arrays::arrayMergeRecursiveOverrule($this->defaults, $matchResults);
  310. return TRUE;
  311. }
  312. /**
  313. * Checks whether $routeValues can be resolved to a corresponding uri.
  314. * If all Route Parts can resolve one or more of the $routeValues, TRUE is
  315. * returned and $this->matchingURI contains the generated URI (excluding
  316. * protocol and host).
  317. *
  318. * @param array $routeValues An array containing key/value pairs to be resolved to uri segments
  319. * @return boolean TRUE if this Route corresponds to the given $routeValues, otherwise FALSE
  320. * @throws \TYPO3\FLOW3\Mvc\Exception\InvalidRoutePartValueException
  321. * @see getMatchingUri()
  322. */
  323. public function resolves(array $routeValues) {
  324. $this->matchingUri = NULL;
  325. if ($this->uriPattern === NULL) {
  326. return FALSE;
  327. }
  328. if (!$this->isParsed) {
  329. $this->parse();
  330. }
  331. $matchingUri = '';
  332. $mergedRouteValues = \TYPO3\FLOW3\Utility\Arrays::arrayMergeRecursiveOverrule($this->defaults, $routeValues);
  333. $requireOptionalRouteParts = FALSE;
  334. $matchingOptionalUriPortion = '';
  335. foreach ($this->routeParts as $routePart) {
  336. if (!$routePart->resolve($routeValues)) {
  337. if (!$routePart->hasDefaultValue()) {
  338. return FALSE;
  339. }
  340. }
  341. $routePartValue = NULL;
  342. if ($routePart->hasValue()) {
  343. $routePartValue = $routePart->getValue();
  344. if (!is_string($routePartValue)) {
  345. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidRoutePartValueException('RoutePart::getValue() must return a string after calling RoutePart::resolve(), got ' . (is_object($routePartValue) ? get_class($routePartValue) : gettype($routePartValue)) . ' for RoutePart "' . get_class($routePart) . '" in Route "' . $this->getName() . '".');
  346. }
  347. }
  348. $routePartDefaultValue = $routePart->getDefaultValue();
  349. if ($routePartDefaultValue !== NULL && !is_string($routePartDefaultValue)) {
  350. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidRoutePartValueException('RoutePart::getDefaultValue() must return a string, got ' . (is_object($routePartDefaultValue) ? get_class($routePartDefaultValue) : gettype($routePartDefaultValue)) . ' for RoutePart "' . get_class($routePart) . '" in Route "' . $this->getName() . '".');
  351. }
  352. if (!$routePart->isOptional()) {
  353. $matchingUri .= $routePart->hasValue() ? $routePartValue : $routePartDefaultValue;
  354. $requireOptionalRouteParts = FALSE;
  355. continue;
  356. }
  357. if ($routePart->hasValue() && $routePartValue !== $routePartDefaultValue) {
  358. $matchingOptionalUriPortion .= $routePartValue;
  359. $requireOptionalRouteParts = TRUE;
  360. } else {
  361. $matchingOptionalUriPortion .= $routePartDefaultValue;
  362. }
  363. if ($requireOptionalRouteParts) {
  364. $matchingUri .= $matchingOptionalUriPortion;
  365. $matchingOptionalUriPortion = '';
  366. }
  367. }
  368. if ($this->compareAndRemoveMatchingDefaultValues($this->defaults, $routeValues) !== TRUE) {
  369. return FALSE;
  370. }
  371. if (isset($routeValues['@format']) && $routeValues['@format'] === '') {
  372. unset($routeValues['@format']);
  373. }
  374. $this->throwExceptionIfTargetControllerDoesNotExist($mergedRouteValues);
  375. // add query string
  376. if (count($routeValues) > 0) {
  377. $routeValues = \TYPO3\FLOW3\Utility\Arrays::removeEmptyElementsRecursively($routeValues);
  378. $routeValues = $this->persistenceManager->convertObjectsToIdentityArrays($routeValues);
  379. if (!$this->appendExceedingArguments) {
  380. $internalArguments = $this->extractInternalArguments($routeValues);
  381. if ($routeValues !== array()) {
  382. return FALSE;
  383. }
  384. $routeValues = $internalArguments;
  385. }
  386. $queryString = http_build_query($routeValues, NULL, '&');
  387. if ($queryString !== '') {
  388. $matchingUri .= strpos($matchingUri, '?') !== FALSE ? '&' . $queryString : '?' . $queryString;
  389. }
  390. }
  391. $this->matchingUri = $matchingUri;
  392. return TRUE;
  393. }
  394. /**
  395. * Recursively iterates through the defaults of this route.
  396. * If a route value is equal to a default value, it's removed
  397. * from $routeValues.
  398. * If a value exists but is not equal to is corresponding default,
  399. * iteration is interrupted and FALSE is returned.
  400. *
  401. * @param array $defaults
  402. * @param array $routeValues
  403. * @return boolean FALSE if one of the $routeValues is not equal to it's default value. Otherwise TRUE
  404. */
  405. protected function compareAndRemoveMatchingDefaultValues(array $defaults, array &$routeValues) {
  406. foreach ($defaults as $key => $defaultValue) {
  407. if (isset($routeValues[$key])) {
  408. if (is_array($defaultValue)) {
  409. if (!is_array($routeValues[$key])) {
  410. return FALSE;
  411. }
  412. return $this->compareAndRemoveMatchingDefaultValues($defaultValue, $routeValues[$key]);
  413. } elseif (is_array($routeValues[$key])) {
  414. return FALSE;
  415. }
  416. if (strtolower($routeValues[$key]) !== strtolower($defaultValue)) {
  417. return FALSE;
  418. }
  419. unset($routeValues[$key]);
  420. }
  421. }
  422. return TRUE;
  423. }
  424. /**
  425. * Removes all internal arguments (prefixed with two underscores) from the given $arguments
  426. * and returns them as array
  427. *
  428. * @param array $arguments
  429. * @return array the internal arguments
  430. */
  431. protected function extractInternalArguments(array &$arguments) {
  432. $internalArguments = array();
  433. foreach ($arguments as $argumentKey => &$argumentValue) {
  434. if (substr($argumentKey, 0, 2) === '__') {
  435. $internalArguments[$argumentKey] = $argumentValue;
  436. unset($arguments[$argumentKey]);
  437. }
  438. if (is_array($argumentValue)) {
  439. $internalArguments[$argumentKey] = $this->extractInternalArguments($argumentValue);
  440. if ($internalArguments[$argumentKey] === array()) {
  441. unset($internalArguments[$argumentKey]);
  442. }
  443. if ($argumentValue === array()) {
  444. unset($arguments[$argumentKey]);
  445. }
  446. }
  447. }
  448. return $internalArguments;
  449. }
  450. /**
  451. * Try to get the controller object name from the given $routeValues and throw an exception, if it can't be resolved.
  452. *
  453. * @param array $routeValues
  454. * @return void
  455. * @throws \TYPO3\FLOW3\Mvc\Routing\Exception\InvalidControllerException
  456. */
  457. protected function throwExceptionIfTargetControllerDoesNotExist(array $routeValues) {
  458. $packageKey = isset($routeValues['@package']) ? $routeValues['@package'] : '';
  459. $subPackageKey = isset($routeValues['@subpackage']) ? $routeValues['@subpackage'] : '';
  460. $controllerName = isset($routeValues['@controller']) ? $routeValues['@controller'] : '';
  461. $controllerObjectName = $this->router->getControllerObjectName($packageKey, $subPackageKey, $controllerName);
  462. if ($controllerObjectName === NULL) {
  463. throw new Exception\InvalidControllerException('No controller object was found for package "' . $packageKey . '", subpackage "' . $subPackageKey . '", controller "' . $controllerName . '" in route "' . $this->getName() . '".', 1301650951);
  464. }
  465. }
  466. /**
  467. * Checks if the given subject contains an object
  468. *
  469. * @param mixed $subject
  470. * @return boolean If it contains an object or not
  471. */
  472. protected function containsObject($subject) {
  473. if (is_object($subject)) {
  474. return TRUE;
  475. }
  476. if (!is_array($subject)) {
  477. return FALSE;
  478. }
  479. foreach ($subject as $value) {
  480. if ($this->containsObject($value)) {
  481. return TRUE;
  482. }
  483. }
  484. return FALSE;
  485. }
  486. /**
  487. * Iterates through all segments in $this->uriPattern and creates
  488. * appropriate RoutePart instances.
  489. *
  490. * @return void
  491. * @throws \TYPO3\FLOW3\Mvc\Exception\InvalidRoutePartHandlerException
  492. * @throws \TYPO3\FLOW3\Mvc\Exception\InvalidUriPatternException
  493. */
  494. public function parse() {
  495. if ($this->isParsed || $this->uriPattern === NULL || $this->uriPattern === '') {
  496. return;
  497. }
  498. $this->routeParts = array();
  499. $currentRoutePartIsOptional = FALSE;
  500. if (substr($this->uriPattern, -1) === '/') {
  501. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" ends with a slash, which is not allowed. You can put the trailing slash in brackets to make it optional.', 1234782997);
  502. }
  503. if ($this->uriPattern[0] === '/') {
  504. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" starts with a slash, which is not allowed.', 1234782983);
  505. }
  506. $matches = array();
  507. preg_match_all(self::PATTERN_EXTRACTROUTEPARTS, $this->uriPattern, $matches, PREG_SET_ORDER);
  508. $lastRoutePart = NULL;
  509. foreach ($matches as $match) {
  510. $routePartType = empty($match['dynamic']) ? self::ROUTEPART_TYPE_STATIC : self::ROUTEPART_TYPE_DYNAMIC;
  511. $routePartName = $match['content'];
  512. if (!empty($match['optionalStart'])) {
  513. if ($lastRoutePart !== NULL && $lastRoutePart->isOptional()) {
  514. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidUriPatternException('the URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains successive optional Route sections, which is not allowed.', 1234562050);
  515. }
  516. $currentRoutePartIsOptional = TRUE;
  517. }
  518. $routePart = NULL;
  519. switch ($routePartType) {
  520. case self::ROUTEPART_TYPE_DYNAMIC:
  521. if ($lastRoutePart instanceof \TYPO3\FLOW3\Mvc\Routing\DynamicRoutePartInterface) {
  522. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidUriPatternException('the URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains successive Dynamic Route Parts, which is not allowed.', 1218446975);
  523. }
  524. if (isset($this->routePartsConfiguration[$routePartName]['handler'])) {
  525. $routePart = $this->objectManager->get($this->routePartsConfiguration[$routePartName]['handler']);
  526. if (!$routePart instanceof \TYPO3\FLOW3\Mvc\Routing\DynamicRoutePartInterface) {
  527. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidRoutePartHandlerException('routePart handlers must implement "\TYPO3\FLOW3\Mvc\Routing\DynamicRoutePartInterface" in route "' . $this->getName() . '"', 1218480972);
  528. }
  529. } elseif (isset($this->routePartsConfiguration[$routePartName]['objectType'])) {
  530. $routePart = new \TYPO3\FLOW3\Mvc\Routing\IdentityRoutePart();
  531. $routePart->setObjectType($this->routePartsConfiguration[$routePartName]['objectType']);
  532. if (isset($this->routePartsConfiguration[$routePartName]['uriPattern'])) {
  533. $routePart->setUriPattern($this->routePartsConfiguration[$routePartName]['uriPattern']);
  534. }
  535. } else {
  536. $routePart = new \TYPO3\FLOW3\Mvc\Routing\DynamicRoutePart();
  537. }
  538. $routePartDefaultValue = \TYPO3\FLOW3\Reflection\ObjectAccess::getPropertyPath($this->defaults, $routePartName);
  539. if ($routePartDefaultValue !== NULL) {
  540. $routePart->setDefaultValue($routePartDefaultValue);
  541. }
  542. break;
  543. case self::ROUTEPART_TYPE_STATIC:
  544. $routePart = new \TYPO3\FLOW3\Mvc\Routing\StaticRoutePart();
  545. if ($lastRoutePart !== NULL && $lastRoutePart instanceof \TYPO3\FLOW3\Mvc\Routing\DynamicRoutePartInterface) {
  546. $lastRoutePart->setSplitString($routePartName);
  547. }
  548. }
  549. $routePart->setName($routePartName);
  550. $routePart->setOptional($currentRoutePartIsOptional);
  551. $routePart->setLowerCase($this->lowerCase);
  552. if (isset($this->routePartsConfiguration[$routePartName]['options'])) {
  553. $routePart->setOptions($this->routePartsConfiguration[$routePartName]['options']);
  554. }
  555. if (isset($this->routePartsConfiguration[$routePartName]['toLowerCase'])) {
  556. $routePart->setLowerCase($this->routePartsConfiguration[$routePartName]['toLowerCase']);
  557. }
  558. $this->routeParts[] = $routePart;
  559. if (!empty($match['optionalEnd'])) {
  560. if (!$currentRoutePartIsOptional) {
  561. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains an unopened optional section.', 1234564495);
  562. }
  563. $currentRoutePartIsOptional = FALSE;
  564. }
  565. $lastRoutePart = $routePart;
  566. }
  567. if ($currentRoutePartIsOptional) {
  568. throw new \TYPO3\FLOW3\Mvc\Exception\InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains an unterminated optional section.', 1234563922);
  569. }
  570. $this->isParsed = TRUE;
  571. }
  572. }
  573. ?>