PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Includes/Decorator/Utils/Operator.php

https://github.com/ckdimka/core
PHP | 447 lines | 143 code | 75 blank | 229 comment | 12 complexity | 2b9c2bbbc8186813de1b4b55005f1568 MD5 | raw file
  1. <?php
  2. // vim: set ts=4 sw=4 sts=4 et:
  3. /**
  4. * LiteCommerce
  5. *
  6. * NOTICE OF LICENSE
  7. *
  8. * This source file is subject to the Open Software License (OSL 3.0)
  9. * that is bundled with this package in the file LICENSE.txt.
  10. * It is also available through the world-wide-web at this URL:
  11. * http://opensource.org/licenses/osl-3.0.php
  12. * If you did not receive a copy of the license and are unable to
  13. * obtain it through the world-wide-web, please send an email
  14. * to licensing@litecommerce.com so we can send you a copy immediately.
  15. *
  16. * @category LiteCommerce
  17. * @package XLite
  18. * @subpackage Decorator
  19. * @author Creative Development LLC <info@cdev.ru>
  20. * @copyright Copyright (c) 2011 Creative Development LLC <info@cdev.ru>. All rights reserved
  21. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  22. * @link http://www.litecommerce.com/
  23. * @see ____file_see____
  24. * @since 1.0.0
  25. */
  26. namespace Includes\Decorator\Utils;
  27. /**
  28. * Operator
  29. *
  30. * @package XLite
  31. * @see ____class_see____
  32. * @since 1.0.0
  33. */
  34. abstract class Operator extends \Includes\Decorator\Utils\AUtils
  35. {
  36. /**
  37. * Suffix for a base class in decorator chain
  38. */
  39. const BASE_CLASS_SUFFIX = 'Abstract';
  40. // ------------------------------ Classes tree -
  41. /**
  42. * Parse all PHP class files and create the graph
  43. *
  44. * @return \Includes\Decorator\DataStructure\Graph\Classes
  45. * @access public
  46. * @see ____func_see____
  47. * @since 1.0.0
  48. */
  49. public static function createClassesTree()
  50. {
  51. // Tree is not a separate data structure - it's only the root node
  52. $root = new \Includes\Decorator\DataStructure\Graph\Classes();
  53. // It's the (<class_name, descriptor>) list
  54. foreach (($index = static::getClassesTreeIndex()) as $node) {
  55. // Three possibilities:
  56. // 1. Class has no parent. Add class to the root
  57. // 2. Class parent is not in the list. Add class to the root
  58. // 3. Class parent defined and exists in the list. Add class to that parent
  59. if (($class = $node->getParentClass()) && isset($index[$class])) {
  60. // Existing non-root parent
  61. $parent = $index[$class];
  62. // Decorator restriction (only for original classes repository)
  63. // :NOTE: do not use the "===" in the second part
  64. if ($parent->isDecorator() && self::STEP_FIRST == static::$step) {
  65. $parent->handleError('It\'s not allowed to extend a decorator', $node);
  66. }
  67. // Cases 2 & 3
  68. $parent->addChild($node);
  69. } else {
  70. // Case 1
  71. $root->addChild($node);
  72. }
  73. }
  74. // Check classes tree integrity
  75. $root->checkIntegrity();
  76. return $root;
  77. }
  78. /**
  79. * Parse PHP files and return plain array with the class descriptors
  80. *
  81. * @return array
  82. * @access protected
  83. * @see ____func_see____
  84. * @since 1.0.0
  85. */
  86. protected static function getClassesTreeIndex()
  87. {
  88. $index = array();
  89. // Iterate over all directories with PHP class files
  90. foreach (static::getClassFileIterator()->getIterator() as $path => $data) {
  91. // Use PHP Tokenizer to search class declaration
  92. if ($class = \Includes\Decorator\Utils\Tokenizer::getFullClassName($path)) {
  93. // File contains a class declaration: add node (descriptor) to the index
  94. $index[$class] = new \Includes\Decorator\DataStructure\Graph\Classes($class, $path);
  95. }
  96. }
  97. return $index;
  98. }
  99. /**
  100. * Get iterator for class files
  101. *
  102. * @return \Includes\Utils\FileFilter
  103. * @access protected
  104. * @see ____func_see____
  105. * @since 1.0.0
  106. */
  107. protected static function getClassFileIterator()
  108. {
  109. return new \Includes\Utils\FileFilter(
  110. static::getClassesDir(),
  111. \Includes\Utils\ModulesManager::getPathPatternForPHP()
  112. );
  113. }
  114. // ------------------------------ Modules graph -
  115. /**
  116. * Check all module dependencies and create the graph
  117. *
  118. * @return \Includes\Decorator\DataStructure\Graph\Modules
  119. * @access protected
  120. * @see ____func_see____
  121. * @since 1.0.0
  122. */
  123. public static function createModulesGraph()
  124. {
  125. // Tree is not a separate data structure - it's only the root node
  126. $root = new \Includes\Decorator\DataStructure\Graph\Modules();
  127. // It's the (<module_name, descriptor>) list
  128. foreach (($index = static::getModulesGraphIndex()) as $node) {
  129. // Two possibilities:
  130. // 1. Module have dependencies. Add module as a child to all its parents
  131. // 2. Module have no dependencies. Add it as a child to the root node
  132. if ($dependencies = $node->getDependencies()) {
  133. // It's the (<module_name>) list
  134. foreach ($dependencies as $module) {
  135. // Case 1 (with dependencies)
  136. $index[$module]->addChild($node);
  137. }
  138. } else {
  139. // Case 2 (without dependencies)
  140. $root->addChild($node);
  141. }
  142. }
  143. // Check modules graph integrity
  144. $root->checkIntegrity();
  145. return $root;
  146. }
  147. /**
  148. * Get all active modules and return plain array with the module descriptors
  149. *
  150. * @return array
  151. * @access protected
  152. * @see ____func_see____
  153. * @since 1.0.0
  154. */
  155. protected static function getModulesGraphIndex()
  156. {
  157. $index = array();
  158. // Fetch all active modules from database.
  159. // Dependencies are checked and corrected by the ModulesManager
  160. foreach (\Includes\Utils\ModulesManager::getActiveModules() as $module => $tmp) {
  161. // Unconditionally add module to the index (since its dependencies are already checked)
  162. $index[$module] = new \Includes\Decorator\DataStructure\Graph\Modules($module);
  163. }
  164. return $index;
  165. }
  166. // ------------------------------ Decorator routines -
  167. /**
  168. * Main decorator callback: build class decoration chains
  169. *
  170. * @param \Includes\Decorator\DataStructure\Graph\Classes $node Current node
  171. *
  172. * @return void
  173. * @access public
  174. * @see ____func_see____
  175. * @since 1.0.0
  176. */
  177. public static function decorateClass(\Includes\Decorator\DataStructure\Graph\Classes $node)
  178. {
  179. // Two kind of node parents: which implement the
  180. // \XLite\Base\IDecorator interface, and regular ones
  181. list($decorators, $regular) = static::divideChildrenIntoGroups($node);
  182. // Do not perform any actions for classes which have no decorators
  183. if ($decorators) {
  184. // We do not need to re-plant first decorator:
  185. // it's already derived from current node
  186. $parent = array_shift($decorators);
  187. // Start from second decorator
  188. foreach ($decorators as $child) {
  189. // Move node to the decorator chain
  190. $child->replant($node, $parent);
  191. // Next step: set new top node in decorator chain
  192. $parent = $child;
  193. }
  194. // Special top-level node: stub class with empty body
  195. $topNode = new \Includes\Decorator\DataStructure\Graph\Classes($node->getClass(), $node->getFile());
  196. $topNode->setTopLevelNodeFlag();
  197. // Add this stub node as a child to the last decorator in the chain
  198. $parent->addChild($topNode);
  199. // Regular children must derive the top-level class in chain
  200. foreach ($regular as $child) {
  201. $child->replant($node, $topNode);
  202. }
  203. // Rename base class to avoid coflicts with the top-level node
  204. $node->setKey($node->getClass() . self::BASE_CLASS_SUFFIX);
  205. $node->setLowLevelNodeFlag();
  206. }
  207. }
  208. /**
  209. * Get decorators and regular children; order the first group
  210. *
  211. * @param \Includes\Decorator\DataStructure\Graph\Classes $node Current node
  212. *
  213. * @return array
  214. * @access public
  215. * @see ____func_see____
  216. * @since 1.0.0
  217. */
  218. protected static function divideChildrenIntoGroups(\Includes\Decorator\DataStructure\Graph\Classes $node)
  219. {
  220. // First element - list of decorators, second one - regular children.
  221. // Both of them may be empty
  222. $result = array(array(), array());
  223. foreach ($node->getChildren() as $child) {
  224. // One in the pair: (<decorators>, ...) or (..., <regular>)
  225. $result[$child->isDecorator() ? 0 : 1][] = $child;
  226. }
  227. // Get module name by class name.
  228. // Calculate module priority in modules graph.
  229. // Use that priority to sort decorators.
  230. // So, classes of dependent modules will be placed above
  231. // their dependencies in decorator chain
  232. usort($result[0], array('static', 'compareClassWeight'));
  233. return $result;
  234. }
  235. /**
  236. * Callback to sort decorators
  237. *
  238. * @param \Includes\Decorator\DataStructure\Graph\Classes $node1 Node to compare (first)
  239. * @param \Includes\Decorator\DataStructure\Graph\Classes $node2 Node to compare (second)
  240. *
  241. * @return integer
  242. * @access protected
  243. * @see ____func_see____
  244. * @since 1.0.0
  245. */
  246. protected static function compareClassWeight(
  247. \Includes\Decorator\DataStructure\Graph\Classes $node1,
  248. \Includes\Decorator\DataStructure\Graph\Classes $node2
  249. ) {
  250. $weight1 = static::getModuleWeight($node1);
  251. $weight2 = static::getModuleWeight($node2);
  252. return ($weight1 === $weight2) ? 0 : (($weight1 < $weight2) ? -1 : 1);
  253. }
  254. /**
  255. * Return class (module) weight by class name
  256. *
  257. * @param \Includes\Decorator\DataStructure\Graph\Classes $node Node to get weight
  258. *
  259. * @return integer
  260. * @access protected
  261. * @see ____func_see____
  262. * @since 1.0.0
  263. */
  264. protected static function getModuleWeight(\Includes\Decorator\DataStructure\Graph\Classes $node)
  265. {
  266. return ($module = $node->getModuleName()) ? static::getModulesGraph()->getCriticalPath($module) : 0;
  267. }
  268. // ------------------------------ Cache writing -
  269. /**
  270. * Write PHP class to the files
  271. *
  272. * @param \Includes\Decorator\DataStructure\Graph\Classes $node Current class node
  273. * @param \Includes\Decorator\DataStructure\Graph\Classes $parent Parent class node
  274. *
  275. * @return void
  276. * @access public
  277. * @see ____func_see____
  278. * @since 1.0.0
  279. */
  280. public static function writeClassFile(
  281. \Includes\Decorator\DataStructure\Graph\Classes $node,
  282. \Includes\Decorator\DataStructure\Graph\Classes $parent = null
  283. ) {
  284. \Includes\Utils\FileManager::write(LC_DIR_CACHE_CLASSES . $node->getPath(), $node->getSource($parent));
  285. }
  286. // ------------------------------ Tags parsing -
  287. /**
  288. * Parse dockblock to get tags
  289. *
  290. * @param string $content String to parse
  291. * @param array $tags Tags to search OPTIONAL
  292. *
  293. * @return array
  294. * @access public
  295. * @see ____func_see____
  296. * @since 1.0.0
  297. */
  298. public static function getTags($content, array $tags = array())
  299. {
  300. $result = array();
  301. if (preg_match_all(static::getTagPattern($tags), $content, $matches)) {
  302. $result += static::parseTags($matches);
  303. }
  304. return $result;
  305. }
  306. /**
  307. * Return pattern to parse source for tags
  308. *
  309. * @param array $tags List of tags to search
  310. *
  311. * @return string
  312. * @access protected
  313. * @see ____func_see____
  314. * @since 1.0.0
  315. */
  316. protected static function getTagPattern(array $tags)
  317. {
  318. $pattern = empty($tags) ? '\w+' : implode('|', $tags);
  319. return '/@(' . $pattern . ')\s*(?:\()?(.*?)\s*(?:\)\s*)?(?=$|^.*@(?:' . $pattern . '))/Smi';
  320. }
  321. /**
  322. * Parse dockblock to get tags
  323. *
  324. * @param array $matches Data from preg_match_all()
  325. *
  326. * @return array
  327. * @access protected
  328. * @see ____func_see____
  329. * @since 1.0.0
  330. */
  331. protected static function parseTags(array $matches)
  332. {
  333. // There are so called "multiple" tags
  334. foreach (array_unique($matches[1]) as $tag) {
  335. // Check if tag is defined only once
  336. if (1 < count($keys = array_keys($matches[1], $tag))) {
  337. $list = array();
  338. // Convert such tag values into the single array
  339. foreach ($keys as $key) {
  340. // Parse list of tag attributes and their values
  341. $list[] = static::parseTagValue($matches[2][$key]);
  342. // To prevent duplicates
  343. unset($matches[1][$key], $matches[2][$key]);
  344. }
  345. // Add tag name and its values to the enf of tags list.
  346. // All existing entries for this tag was cleared by the "unset()"
  347. $matches[1][] = $tag;
  348. $matches[2][] = $list;
  349. // If the value was parsed (the corresponded tokens were found), change its type to the "array"
  350. // TODO: check if there is a more convenient approach to manage "multiple" tags
  351. } elseif ($matches[2][$key = array_shift($keys)] !== ($value = static::parseTagValue($matches[2][$key]))) {
  352. $matches[2][$key] = array($value ?: $matches[2][$key]);
  353. }
  354. }
  355. // Create an associative array of tag names and their values
  356. return array_combine(array_map('strtolower', $matches[1]), $matches[2]);
  357. }
  358. /**
  359. * Parse value of a phpDocumenter tag
  360. *
  361. * @param string $value Value to parse
  362. *
  363. * @return array
  364. * @access protected
  365. * @see ____func_see____
  366. * @since 1.0.0
  367. */
  368. protected static function parseTagValue($value)
  369. {
  370. return \Includes\Utils\Converter::parseQuery($value, '=', ',', '"\'');
  371. }
  372. }