PageRenderTime 37ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/core/classes/framework/route/route.php

https://github.com/GinoPane/fantasia
PHP | 778 lines | 306 code | 78 blank | 394 comment | 38 complexity | f98070b18d604e8d4e2ad0ccfdce5a76 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * Instant part of routing system
  5. *
  6. * @version 1.0
  7. *
  8. * @author Sergey Karavay <sergey.karavay@gmail.com>
  9. */
  10. class Route
  11. {
  12. /**
  13. *
  14. * Route keyname
  15. *
  16. * @var string
  17. */
  18. private $_key = "";
  19. /**
  20. * Pattern string for route as it is in configuration
  21. *
  22. * @var string
  23. */
  24. private $_fullPattern = "";
  25. /**
  26. * Pattern string for route without parameters
  27. *
  28. * @var string
  29. */
  30. private $_clearPattern = "";
  31. /**
  32. *
  33. * @var array
  34. */
  35. private $_clearPatternArray = array();
  36. /**
  37. *
  38. * Packet to link with
  39. *
  40. * @var string
  41. */
  42. private $_packName = "";
  43. /**
  44. *
  45. * Component name for this route
  46. *
  47. * @var string
  48. */
  49. private $_componentName = "";
  50. /**
  51. *
  52. * Manager class name to link with
  53. *
  54. * @var string
  55. */
  56. private $_controllerName = "";
  57. /**
  58. *
  59. * Action to execute within the page. May be empty if page
  60. * decides everything about actions internally
  61. *
  62. * @var string
  63. */
  64. private $_action = "";
  65. /**
  66. *
  67. * Array of page parameter names to be passed
  68. *
  69. * @var array
  70. */
  71. private $_parameters = array();
  72. /**
  73. *
  74. * Array of parameters that passed through validator
  75. *
  76. * @var array
  77. */
  78. private $_parametersMatchedValidator = array();
  79. /**
  80. * Array of validation patterns for variables
  81. *
  82. * @var array
  83. */
  84. private $_validator = array();
  85. /**
  86. * Array of default variable values
  87. *
  88. *@var array
  89. */
  90. private $_defaults = array();
  91. /**
  92. *
  93. * HTTP method used to access the page.
  94. * If not set then it's "GET"
  95. *
  96. * @var string
  97. */
  98. private $_httpMethod = "";
  99. /**
  100. *
  101. * If this route is for ajax calls
  102. *
  103. * @var boolean
  104. */
  105. private $_ajaxRoute = false;
  106. /**
  107. *
  108. * Defines whether page is in secured area
  109. *
  110. * @var bool
  111. */
  112. private $_secured = false;
  113. /**
  114. * Creates the Route class instance
  115. *
  116. * @param string $pattern pattern that matches route
  117. * @param string $packComponent pack:component pair
  118. *
  119. * @example
  120. *
  121. * <pre><code>
  122. *
  123. * $route = new Route("some name", "page/nested-page/{param1}/{param2}", "main:index", "view");
  124. *
  125. * $route->setValidation(array("param1" =>
  126. * array(Validator::IS_PATTERN_STRING =>
  127. * array('pattern' => "[abc]+")),
  128. * "param2" =>
  129. * array(Validator::IS_NUMERIC =>
  130. * array(true))));
  131. *
  132. * $route->setDefaults(array( "param1" => "abcabc",
  133. * "param2" => 13));
  134. *
  135. * </code></pre>
  136. *
  137. * @throws Exception
  138. *
  139. * @return Route
  140. */
  141. public function __construct($key, $pattern, $packComponent = "")
  142. {
  143. $this->_key = strval($key);
  144. $routeParts = array();
  145. $pattern = self::transliterate($pattern);
  146. if (!is_string($pattern)
  147. || !is_string($packComponent)
  148. || (((false == preg_match_all("/{([a-z0-9]+)}|([a-z0-9]+)/i", $pattern, $routeParts))) && ("" !== $pattern)))
  149. {
  150. throw new Exception("Wrong variables passed");
  151. }
  152. /**
  153. *
  154. * for root_part_1/root_part_2/root_part_3/param_1/param_2
  155. *
  156. * $routeParts should be:
  157. *
  158. * array(3) {
  159. [0]=>
  160. array(5) {
  161. [0]=>
  162. string(5) "root_part_1"
  163. [1]=>
  164. string(5) "root_part_2"
  165. [2]=>
  166. string(5) "root_part_3"
  167. [3]=>
  168. string(6) "{param_1}"
  169. [4]=>
  170. string(6) "{param_2}"
  171. }
  172. [1]=>
  173. array(5) {
  174. [0]=>
  175. string(0) ""
  176. [1]=>
  177. string(0) ""
  178. [2]=>
  179. string(0) ""
  180. [3]=>
  181. string(4) "param_1"
  182. [4]=>
  183. string(4) "param_2"
  184. }
  185. [2]=>
  186. array(5) {
  187. [0]=>
  188. string(5) "root_part_1"
  189. [1]=>
  190. string(5) "root_part_2"
  191. [2]=>
  192. string(5) "root_part_3"
  193. [3]=>
  194. string(0) ""
  195. [4]=>
  196. string(0) ""
  197. }
  198. }
  199. */
  200. if (isset($routeParts[0]))
  201. $this->_fullPattern = implode("/", array_filter($routeParts[0])) . "/";
  202. else
  203. $this->_fullPattern = "/";
  204. if (isset($routeParts[2]))
  205. $this->_clearPattern = implode("/", array_filter($routeParts[2])) . "/";
  206. else
  207. $this->_clearPattern = "/";
  208. if (strstr($packComponent, Cfg_App::PACK_COMPONENT_SEPARATOR))
  209. {
  210. $pageParts = explode(Cfg_App::PACK_COMPONENT_SEPARATOR, $packComponent);
  211. $this->_packName = $pageParts[0];
  212. $this->_componentName = $pageParts[1];
  213. }
  214. else
  215. {
  216. $this->_packName = App::getCurrentPackName();
  217. $this->_componentName = $packComponent;
  218. }
  219. $parameters = array();
  220. if (preg_match_all("/{([a-z0-9]+)}/i", $pattern, $parameters))
  221. {
  222. $this->_parameters = array_values($parameters[1]);
  223. // foreach($this->_parameters as $param)
  224. // {
  225. // $this->_defaults[$param] = "";
  226. // }
  227. }
  228. return $this;
  229. }
  230. /**
  231. *
  232. * Returns route as array of it's parts without parameters
  233. *
  234. * @return array
  235. */
  236. public function getRouteArray()
  237. {
  238. if (!$this->_clearPatternArray)
  239. {
  240. $this->_clearPatternArray = self::getUrlArray($this->_clearPattern);
  241. }
  242. return $this->_clearPatternArray;
  243. }
  244. /**
  245. *
  246. * Returns route array depth
  247. *
  248. * @return int
  249. */
  250. public function getRouteArrayDepth()
  251. {
  252. if (!$this->_clearPatternArray)
  253. {
  254. $this->getRouteArray();
  255. }
  256. return count($this->_clearPatternArray);
  257. }
  258. /**
  259. *
  260. * Returns route key
  261. *
  262. * @return string
  263. */
  264. public function getKey()
  265. {
  266. return $this->_key;
  267. }
  268. /**
  269. *
  270. * Returns route pattern without parameters
  271. *
  272. * @return string
  273. */
  274. public function getClearPattern()
  275. {
  276. return $this->_clearPattern;
  277. }
  278. /**
  279. *
  280. * Returns http method, that should be used for route
  281. *
  282. * @return string GET | POST
  283. */
  284. public function getHttpMethod()
  285. {
  286. return $this->_httpMethod ? $this->_httpMethod : "GET";
  287. }
  288. /**
  289. *
  290. * Sets http method to access this route
  291. *
  292. */
  293. public function setHttpMethod($method)
  294. {
  295. if (in_array($method, Cfg_App::$HTTP_METHODS))
  296. {
  297. $this->_httpMethod = $method;
  298. }
  299. else
  300. {
  301. trigger_error("\{$method}\ is not within supported http methods", E_USER_NOTICE);
  302. }
  303. }
  304. /**
  305. *
  306. * Sets the ajaxRoute flag, if $isAjax parameter presents,
  307. * gets the ajaxRoute flag otherwise
  308. *
  309. * @param boolean $isAjax
  310. * @return boolean
  311. */
  312. public function isAjax($isAjax = null)
  313. {
  314. if (is_null($isAjax))
  315. {
  316. return $this->_ajaxRoute;
  317. }
  318. else
  319. {
  320. $this->_ajaxRoute = (bool)$isAjax;
  321. }
  322. }
  323. /**
  324. * Sets the secured flag, if $isSecured parameter presents,
  325. * gets the secured flag otherwise
  326. *
  327. * @param boolean $isSecured
  328. * @return boolean
  329. */
  330. public function isSecured($isSecured = null)
  331. {
  332. if (is_null($isSecured))
  333. {
  334. return $this->_secured;
  335. }
  336. else
  337. {
  338. $this->_secured = (bool)$isSecured;
  339. }
  340. }
  341. /**
  342. *
  343. * Returns count of parameters for route
  344. *
  345. * @return int
  346. */
  347. public function getParametersCount()
  348. {
  349. return count($this->_parameters);
  350. }
  351. /**
  352. *
  353. * Returns controller class name
  354. *
  355. * @return string
  356. */
  357. public function getControllerName()
  358. {
  359. if (!$this->_controllerName){
  360. $this->setControllerName(Cfg_App::getPackOption(Cfg_App::KEY_DEFAULT_CONTROLLER_NAME, $this->getPackName()));
  361. }
  362. return $this->_controllerName;
  363. }
  364. /**
  365. *
  366. * @param type $controllerName
  367. * @return \Route
  368. */
  369. public function setControllerName($controllerName)
  370. {
  371. if (!preg_match('/controller$/i', $controllerName))
  372. {
  373. $controllerName .= 'Controller';
  374. }
  375. $this->_controllerName = $controllerName;
  376. return $this;
  377. }
  378. /**
  379. *
  380. * Returns component name
  381. *
  382. * @return string
  383. */
  384. public function getComponentName()
  385. {
  386. return $this->_componentName;
  387. }
  388. /**
  389. *
  390. * Sets route's component name
  391. *
  392. * @param type $componentName
  393. * @return \Route
  394. */
  395. public function setComponentName($componentName)
  396. {
  397. $this->_componentName = $componentName;
  398. return $this;
  399. }
  400. /**
  401. *
  402. * Returns pack name for this route
  403. *
  404. * @return string
  405. */
  406. public function getPackName()
  407. {
  408. return $this->_packName;
  409. }
  410. /**
  411. *
  412. * Sets the validators for parameter patterns
  413. *
  414. * @param array $validators
  415. *
  416. * @example
  417. *
  418. * <pre><code>
  419. *
  420. * $route->setValidation(array(
  421. * "param1" =>
  422. * array(Validator::IS_PATTERN_STRING =>
  423. * array('pattern' => "[abc]+")),
  424. * "param2" =>
  425. * array(Validator::IS_NUMERIC =>
  426. * array(true)))
  427. * );
  428. *
  429. * </code></pre>
  430. *
  431. * @throws Exception
  432. *
  433. * @return Route
  434. */
  435. public function setValidation(array $validators = array())
  436. {
  437. foreach($validators as $var => $validatorPattern)
  438. {
  439. if (in_array($var, $this->_parameters))
  440. {
  441. $this->_validator[$var] = $validatorPattern;
  442. }
  443. }
  444. return $this->_checkConsistency();
  445. }
  446. /**
  447. *
  448. * Return true, if validation enabled
  449. *
  450. * @return bool
  451. */
  452. public function validationEnabled()
  453. {
  454. return (bool)$this->_filter;
  455. }
  456. /**
  457. *
  458. * Returns count of parameters that passed through validation
  459. *
  460. * @return int
  461. */
  462. public function getParametersMatchedCount()
  463. {
  464. return count($this->_parametersMatchedValidator);
  465. }
  466. /**
  467. *
  468. * Returns parameter value associated with passed $key. If wrong parameter passed
  469. * default value will be returned. If parameter not passed or not filled by defaults null value will be returned then
  470. *
  471. * @param type $key
  472. *
  473. * @return mixed|null
  474. */
  475. public function getParameter($key)
  476. {
  477. if (in_array($key, $this->_parameters)){
  478. if (isset($this->_parametersMatchedValidator[$key])){
  479. return $this->_parametersMatchedValidator[$key];
  480. } elseif (isset($this->_defaults[$key])) {
  481. return $this->_defaults[$key];
  482. } else {
  483. return null;
  484. }
  485. } else {
  486. return null;
  487. }
  488. }
  489. public function getParameters()
  490. {
  491. }
  492. /**
  493. *
  494. * Returns count of parameters that are not covered by default values
  495. *
  496. * @return int count of parameters without defaults
  497. */
  498. public function getDefaultsCoverage()
  499. {
  500. return count(array_diff($this->_parameters, array_keys($this->_defaults)));
  501. }
  502. /**
  503. *
  504. * Returns count of parameters that have validators
  505. *
  506. * @return int count of parameters under validation
  507. */
  508. public function getValidationCoverage()
  509. {
  510. return count($this->_validator);
  511. }
  512. /**
  513. *
  514. * Sets the default values for page parameters
  515. *
  516. * @param array $defaults
  517. *
  518. * @throws Exception
  519. *
  520. * @return Route
  521. */
  522. public function setDefaults(array $defaults = array())
  523. {
  524. foreach($defaults as $var => $defaultValue)
  525. {
  526. if (in_array($var, $this->_parameters))
  527. {
  528. $this->_defaults[$var] = $defaultValue;
  529. }
  530. }
  531. return $this->_checkConsistency();
  532. }
  533. /**
  534. *
  535. * Sets route controller action
  536. *
  537. * @param string $action
  538. * @return Route
  539. */
  540. public function setActionName($action)
  541. {
  542. $this->_action = $action;
  543. return $this;
  544. }
  545. /**
  546. *
  547. * Returns action name to be called
  548. *
  549. * @return string Action name
  550. */
  551. public function getActionName()
  552. {
  553. if ($this->_action && is_string($this->_action))
  554. {
  555. return $this->_action;
  556. }
  557. else
  558. {
  559. return Cfg_App::getPackOption(Cfg_App::KEY_DEFAULT_ACTION_NAME, $this->getPackName());
  560. }
  561. }
  562. /**
  563. *
  564. * Returns calculated difficulty of ValidatorS
  565. *
  566. * @return int calculated difficulty of all Validators for this route
  567. */
  568. public function getValidationDifficulty()
  569. {
  570. return Validator::getValidationDifficulty($this->_validator);
  571. }
  572. /**
  573. *
  574. * @return Route
  575. */
  576. private function _checkConsistency()
  577. {
  578. if ($this->_parameters && $this->_validator && $this->_defaults)
  579. {
  580. foreach($this->_parameters as $parameter)
  581. {
  582. if (isset($this->_defaults[$parameter]) && $this->_defaults[$parameter] &&
  583. isset($this->_validator[$parameter]))
  584. {
  585. if (!Validator::passMultiValidatorValue($this->_defaults[$parameter],
  586. $this->_validator[$parameter]))
  587. {
  588. throw new Exception("Wrong default parameters passed");
  589. }
  590. }
  591. }
  592. }
  593. return $this;
  594. }
  595. /**
  596. *
  597. * Validates parameters passed to the route
  598. *
  599. * @param array $urlArray
  600. *
  601. * @return array [0] => count of parameters passed to script,
  602. * [1] => count of parameters that passed the validator,
  603. * [2] => count of parameters that were not filled from defaults array
  604. */
  605. public function validateParameters($urlArray)
  606. {
  607. $freeParameters = array_values(array_diff($urlArray, $this->getRouteArray()));
  608. //echo "<br /> free params " .var_dump($urlArray).var_dump($this->getRouteArray()). var_dump($freeParameters) . "<br />";
  609. $notFilledByDefaultsCount = 0;
  610. $this->_parametersMatchedValidator = array();
  611. //parameters passed to script
  612. $count = $this->getParametersCount() > count($freeParameters) ? count($freeParameters) : $this->getParametersCount();
  613. $result = true;
  614. for($i = 0; $i < $count; $i++)
  615. {
  616. if (isset($this->_validator[$this->_parameters[$i]]))
  617. {
  618. $result &= Validator::passMultiValidatorValue($freeParameters[$i],
  619. $this->_validator[$this->_parameters[$i]]);
  620. }
  621. if ($result)
  622. {
  623. $this->_parametersMatchedValidator[$this->_parameters[$i]] = $freeParameters[$i];
  624. }
  625. else
  626. {
  627. break;
  628. }
  629. }
  630. if ($result && ($count < $this->getParametersCount()))
  631. {
  632. $notPassedParameters = array_diff($this->_parameters, array_keys($this->_parametersMatchedValidator));
  633. $notFilledByDefaultsCount = count(array_diff($notPassedParameters, array_keys($this->_defaults)));
  634. }
  635. //var_dump($freeParameters,count($freeParameters),count($this->_parametersMatchedFilter), $notFilledByDefaultsCount);
  636. return array(count($freeParameters), count($this->_parametersMatchedValidator), $notFilledByDefaultsCount);
  637. }
  638. /**
  639. *
  640. * "Compares" routes; longer routes
  641. * are more preferable, but route with less parameters
  642. * is more likely to be used,
  643. * so it should be closer to the top;
  644. *
  645. * @param Route $a
  646. * @param Route $b
  647. * @return int
  648. */
  649. public static function compare(Route $a, Route $b)
  650. {
  651. if ($a->getClearPattern() == $b->getClearPattern())
  652. {
  653. return ($a->getParametersCount() < $b->getParametersCount()) ? -1 : 1;
  654. }
  655. else
  656. {
  657. return ($a->getClearPattern() > $b->getClearPattern()) ? -1 : 1;
  658. }
  659. }
  660. /**
  661. *
  662. * Returns an array of url parts
  663. *
  664. * @example
  665. * /path1/path2/path3/ => array('path1', 'path2', 'path3')
  666. *
  667. * @param string $url url string to be converted to array
  668. * @return array
  669. *
  670. * @throws Exception
  671. */
  672. public static function getUrlArray($url)
  673. {
  674. if (!is_string($url))
  675. {
  676. throw new Exception("Url must be a string");
  677. }
  678. $url = self::transliterate($url);
  679. $urlParts = array_map('trim', array_filter(explode("/", $url)));
  680. array_unshift($urlParts, "/");
  681. return $urlParts;
  682. }
  683. /**
  684. *
  685. * One-way transliteration function for cyrrilic support
  686. *
  687. * @param string $text Cyrillic text
  688. * @return string Transliterated text
  689. */
  690. public static function transliterate($text) {
  691. $cyrrilic = array(
  692. 'а', 'б', 'в', 'г', 'д', 'e', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я',
  693. );
  694. $latin = array(
  695. 'a', 'b', 'v', 'g', 'd', 'e', 'yo', 'zh', 'z', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'f', 'h', 'c', 'ch', 'sh', 'sht', '', 'y', '', 'e', 'yu', 'ya',
  696. );
  697. return str_ireplace($cyrrilic, $latin, $text);
  698. }
  699. }
  700. ?>