Classes.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. /**
  3. * A helper for handling Magento-specific class names in various use cases
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Framework\App\Utility;
  9. use Magento\Framework\Component\ComponentRegistrar;
  10. /**
  11. * Utility for class names processing
  12. */
  13. class Classes
  14. {
  15. /**
  16. * virtual class declarations collected from the whole system
  17. *
  18. * @var array
  19. */
  20. protected static $_virtualClasses = [];
  21. /**
  22. * Find all unique matches in specified content using specified PCRE
  23. *
  24. * @param string $contents
  25. * @param string $regex
  26. * @param array &$result
  27. * @return array
  28. */
  29. public static function getAllMatches($contents, $regex, &$result = [])
  30. {
  31. preg_match_all($regex, $contents, $matches);
  32. array_shift($matches);
  33. foreach ($matches as $row) {
  34. $result = array_merge($result, $row);
  35. }
  36. $result = array_filter(
  37. array_unique($result),
  38. function ($value) {
  39. return !empty($value);
  40. }
  41. );
  42. return $result;
  43. }
  44. /**
  45. * Get XML node text values using specified xPath
  46. *
  47. * The node must contain specified attribute
  48. *
  49. * @param \SimpleXMLElement $xml
  50. * @param string $xPath
  51. * @return array
  52. */
  53. public static function getXmlNodeValues(\SimpleXMLElement $xml, $xPath)
  54. {
  55. $result = [];
  56. $nodes = $xml->xpath($xPath) ?: [];
  57. foreach ($nodes as $node) {
  58. $result[] = (string)$node;
  59. }
  60. return $result;
  61. }
  62. /**
  63. * Get XML node names using specified xPath
  64. *
  65. * @param \SimpleXMLElement $xml
  66. * @param string $xpath
  67. * @return array
  68. */
  69. public static function getXmlNodeNames(\SimpleXMLElement $xml, $xpath)
  70. {
  71. $result = [];
  72. $nodes = $xml->xpath($xpath) ?: [];
  73. foreach ($nodes as $node) {
  74. $result[] = $node->getName();
  75. }
  76. return $result;
  77. }
  78. /**
  79. * Get XML node attribute values using specified xPath
  80. *
  81. * @param \SimpleXMLElement $xml
  82. * @param string $xPath
  83. * @param string $attributeName
  84. * @return array
  85. */
  86. public static function getXmlAttributeValues(\SimpleXMLElement $xml, $xPath, $attributeName)
  87. {
  88. $result = [];
  89. $nodes = $xml->xpath($xPath) ?: [];
  90. foreach ($nodes as $node) {
  91. $node = (array)$node;
  92. if (isset($node['@attributes'][$attributeName])) {
  93. $result[] = $node['@attributes'][$attributeName];
  94. }
  95. }
  96. return $result;
  97. }
  98. /**
  99. * Extract class name from a conventional callback specification "Class::method"
  100. *
  101. * @param string $callbackName
  102. * @return string
  103. */
  104. public static function getCallbackClass($callbackName)
  105. {
  106. $class = explode('::', $callbackName);
  107. return $class[0];
  108. }
  109. /**
  110. * Find classes in a configuration XML-file (assumes any files under Namespace/Module/etc/*.xml)
  111. *
  112. * @param \SimpleXMLElement $xml
  113. * @return array
  114. */
  115. public static function collectClassesInConfig(\SimpleXMLElement $xml)
  116. {
  117. // @todo this method must be refactored after implementation of MAGETWO-7689 (valid configuration)
  118. $classes = self::getXmlNodeValues(
  119. $xml,
  120. '
  121. /config//resource_adapter | /config/*[not(name()="sections")]//class[not(ancestor::observers)]
  122. | //model[not(parent::connection)] | //backend_model | //source_model | //price_model
  123. | //model_token | //writer_model | //clone_model | //frontend_model | //working_model
  124. | //admin_renderer | //renderer | /config/*/di/preferences/*'
  125. );
  126. $classes = array_merge($classes, self::getXmlAttributeValues($xml, '//@backend_model', 'backend_model'));
  127. $classes = array_merge(
  128. $classes,
  129. self::getXmlNodeNames(
  130. $xml,
  131. '/logging/*/expected_models/* | /logging/*/actions/*/expected_models/* | /config/*/di/preferences/*'
  132. )
  133. );
  134. $classes = array_map([\Magento\Framework\App\Utility\Classes::class, 'getCallbackClass'], $classes);
  135. $classes = array_map('trim', $classes);
  136. $classes = array_unique($classes);
  137. $classes = array_filter(
  138. $classes,
  139. function ($value) {
  140. return !empty($value);
  141. }
  142. );
  143. return $classes;
  144. }
  145. /**
  146. * Find classes in a layout configuration XML-file
  147. *
  148. * @param \SimpleXMLElement $xml
  149. * @return array
  150. */
  151. public static function collectLayoutClasses(\SimpleXMLElement $xml)
  152. {
  153. $classes = self::getXmlAttributeValues($xml, '/layout//block[@class]', 'class');
  154. $classes = array_merge(
  155. $classes,
  156. self::getXmlNodeValues(
  157. $xml,
  158. '/layout//action/attributeType | /layout//action[@method="addTab"]/content
  159. | /layout//action[@method="addMergeSettingsBlockType"
  160. or @method="addInformationRenderer"
  161. or @method="addDatabaseBlock"]/*[2]
  162. | /layout//action[@method="setMassactionBlockName"]/name
  163. | /layout//action[@method="setEntityModelClass"]/code'
  164. )
  165. );
  166. return array_unique($classes);
  167. }
  168. /**
  169. * Scan application source code and find classes
  170. *
  171. * Sub-type pattern allows to distinguish "type" of a class within a module (for example, Block, Model)
  172. * Returns array(<class> => <module>)
  173. *
  174. * @param string $subTypePattern
  175. * @return array
  176. */
  177. public static function collectModuleClasses($subTypePattern = '[A-Za-z]+')
  178. {
  179. $componentRegistrar = new ComponentRegistrar();
  180. $result = [];
  181. foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $modulePath) {
  182. $pattern = '/^' . preg_quote($modulePath, '/') . '\/(' . $subTypePattern . '\/.+)\.php$/';
  183. foreach (Files::init()->getFiles([$modulePath], '*.php') as $file) {
  184. if (preg_match($pattern, $file)) {
  185. $partialFileName = substr($file, strlen($modulePath) + 1);
  186. $partialFileName = substr($partialFileName, 0, strlen($partialFileName) - strlen('.php'));
  187. $partialClassName = str_replace('/', '\\', $partialFileName);
  188. $className = str_replace('_', '\\', $moduleName) . '\\' . $partialClassName;
  189. $result[$className] = $moduleName;
  190. }
  191. }
  192. }
  193. return $result;
  194. }
  195. /**
  196. * Fetch virtual class declarations from DI configs
  197. *
  198. * @return array
  199. */
  200. public static function getVirtualClasses()
  201. {
  202. if (!empty(self::$_virtualClasses)) {
  203. return self::$_virtualClasses;
  204. }
  205. $configFiles = Files::init()->getDiConfigs();
  206. foreach ($configFiles as $fileName) {
  207. $configDom = new \DOMDocument();
  208. $configDom->load($fileName);
  209. $xPath = new \DOMXPath($configDom);
  210. $vTypes = $xPath->query('/config/virtualType');
  211. /** @var \DOMNode $virtualType */
  212. foreach ($vTypes as $virtualType) {
  213. $name = $virtualType->attributes->getNamedItem('name')->textContent;
  214. if (!$virtualType->attributes->getNamedItem('type')) {
  215. continue;
  216. }
  217. $type = $virtualType->attributes->getNamedItem('type')->textContent;
  218. self::$_virtualClasses[$name] = $type;
  219. }
  220. }
  221. return self::$_virtualClasses;
  222. }
  223. /**
  224. * Check if instance is virtual type
  225. *
  226. * @param string $className
  227. * @return bool
  228. */
  229. public static function isVirtual($className)
  230. {
  231. //init virtual classes if necessary
  232. self::getVirtualClasses();
  233. return array_key_exists($className, self::$_virtualClasses);
  234. }
  235. /**
  236. * Get real type name for virtual type
  237. *
  238. * @param string $className
  239. * @return string
  240. */
  241. public static function resolveVirtualType($className)
  242. {
  243. if (false == self::isVirtual($className)) {
  244. return $className;
  245. }
  246. $resolvedName = self::$_virtualClasses[$className];
  247. return self::resolveVirtualType($resolvedName);
  248. }
  249. /**
  250. * Check class is auto-generated
  251. *
  252. * @param string $className
  253. * @return bool
  254. */
  255. public static function isAutogenerated($className)
  256. {
  257. if (preg_match(
  258. '/.*\\\\[a-zA-Z0-9]{1,}(Factory|SearchResults|DataBuilder|Extension|ExtensionInterface)$/',
  259. $className
  260. )
  261. || preg_match('/Magento\\\\[\w]+\\\\(Test\\\\(Page|Fixture))\\\\/', $className)
  262. || preg_match('/.*\\\\[a-zA-Z0-9]{1,}\\\\Proxy$/', $className)
  263. ) {
  264. return true;
  265. }
  266. return false;
  267. }
  268. /**
  269. * Scan contents as PHP-code and find class name occurrences
  270. *
  271. * @param string $contents
  272. * @param array &$classes
  273. * @return array
  274. */
  275. public static function collectPhpCodeClasses($contents, &$classes = [])
  276. {
  277. self::getAllMatches(
  278. $contents,
  279. '/
  280. # ::getModel ::getSingleton ::getResourceModel ::getResourceSingleton
  281. \:\:get(?:Resource)?(?:Model | Singleton)\(\s*[\'"]([^\'"]+)[\'"]\s*[\),]
  282. # addBlock createBlock getBlockSingleton
  283. | (?:addBlock | createBlock | getBlockSingleton)\(\s*[\'"]([^\'"]+)[\'"]\s*[\),]
  284. # various methods, first argument
  285. | \->(?:initReport | setEntityModelClass
  286. | setAttributeModel | setBackendModel | setFrontendModel | setSourceModel | setModel
  287. )\(\s*[\'"]([^\'"]+)[\'"]\s*[\),]
  288. # various methods, second argument
  289. | \->add(?:ProductConfigurationHelper | OptionsRenderCfg)\(.+,\s*[\'"]([^\'"]+)[\'"]\s*[\),]
  290. # models in install or setup
  291. | [\'"](?:resource_model | attribute_model | entity_model | entity_attribute_collection
  292. | source | backend | frontend | input_renderer | frontend_input_renderer
  293. )[\'"]\s*=>\s*[\'"]([^\'"]+)[\'"]
  294. # misc
  295. | function\s_getCollectionClass\(\)\s+{\s+return\s+[\'"]([a-z\d_\/]+)[\'"]
  296. | (?:_parentResourceModelName | _checkoutType | _apiType)\s*=\s*\'([a-z\d_\/]+)\'
  297. | \'renderer\'\s*=>\s*\'([a-z\d_\/]+)\'
  298. | protected\s+\$_(?:form|info|backendForm|iframe)BlockType\s*=\s*[\'"]([^\'"]+)[\'"]
  299. /Uix',
  300. $classes
  301. );
  302. // check ->_init | parent::_init
  303. $skipForInit = implode(
  304. '|',
  305. [
  306. 'id',
  307. '[\w\d_]+_id',
  308. 'pk',
  309. 'code',
  310. 'status',
  311. 'serial_number',
  312. 'entity_pk_value',
  313. 'currency_code',
  314. 'unique_key'
  315. ]
  316. );
  317. self::getAllMatches(
  318. $contents,
  319. '/
  320. (?:parent\:\: | \->)_init\(\s*[\'"]([^\'"]+)[\'"]\s*\)
  321. | (?:parent\:\: | \->)_init\(\s*[\'"]([^\'"]+)[\'"]\s*,\s*[\'"]((?!(' .
  322. $skipForInit .
  323. '))[^\'"]+)[\'"]\s*\)
  324. /Uix',
  325. $classes
  326. );
  327. return $classes;
  328. }
  329. /**
  330. * Retrieve module name by class
  331. *
  332. * @param string $class
  333. * @return string
  334. */
  335. public static function getClassModuleName($class)
  336. {
  337. $parts = explode('\\', trim($class, '\\'));
  338. return $parts[0] . '_' . $parts[1];
  339. }
  340. }