xpath($xPath) ?: []; foreach ($nodes as $node) { $result[] = (string)$node; } return $result; } /** * Get XML node names using specified xPath * * @param \SimpleXMLElement $xml * @param string $xpath * @return array */ public static function getXmlNodeNames(\SimpleXMLElement $xml, $xpath) { $result = []; $nodes = $xml->xpath($xpath) ?: []; foreach ($nodes as $node) { $result[] = $node->getName(); } return $result; } /** * Get XML node attribute values using specified xPath * * @param \SimpleXMLElement $xml * @param string $xPath * @param string $attributeName * @return array */ public static function getXmlAttributeValues(\SimpleXMLElement $xml, $xPath, $attributeName) { $result = []; $nodes = $xml->xpath($xPath) ?: []; foreach ($nodes as $node) { $node = (array)$node; if (isset($node['@attributes'][$attributeName])) { $result[] = $node['@attributes'][$attributeName]; } } return $result; } /** * Extract class name from a conventional callback specification "Class::method" * * @param string $callbackName * @return string */ public static function getCallbackClass($callbackName) { $class = explode('::', $callbackName); return $class[0]; } /** * Find classes in a configuration XML-file (assumes any files under Namespace/Module/etc/*.xml) * * @param \SimpleXMLElement $xml * @return array */ public static function collectClassesInConfig(\SimpleXMLElement $xml) { // @todo this method must be refactored after implementation of MAGETWO-7689 (valid configuration) $classes = self::getXmlNodeValues( $xml, ' /config//resource_adapter | /config/*[not(name()="sections")]//class[not(ancestor::observers)] | //model[not(parent::connection)] | //backend_model | //source_model | //price_model | //model_token | //writer_model | //clone_model | //frontend_model | //working_model | //admin_renderer | //renderer | /config/*/di/preferences/*' ); $classes = array_merge($classes, self::getXmlAttributeValues($xml, '//@backend_model', 'backend_model')); $classes = array_merge( $classes, self::getXmlNodeNames( $xml, '/logging/*/expected_models/* | /logging/*/actions/*/expected_models/* | /config/*/di/preferences/*' ) ); $classes = array_map(['Magento\Framework\App\Utility\Classes', 'getCallbackClass'], $classes); $classes = array_map('trim', $classes); $classes = array_unique($classes); $classes = array_filter( $classes, function ($value) { return !empty($value); } ); return $classes; } /** * Find classes in a layout configuration XML-file * * @param \SimpleXMLElement $xml * @return array */ public static function collectLayoutClasses(\SimpleXMLElement $xml) { $classes = self::getXmlAttributeValues($xml, '/layout//block[@class]', 'class'); $classes = array_merge( $classes, self::getXmlNodeValues( $xml, '/layout//action/attributeType | /layout//action[@method="addTab"]/content | /layout//action[@method="addMergeSettingsBlockType" or @method="addInformationRenderer" or @method="addDatabaseBlock"]/*[2] | /layout//action[@method="setMassactionBlockName"]/name | /layout//action[@method="setEntityModelClass"]/code' ) ); return array_unique($classes); } /** * Scan application source code and find classes * * Sub-type pattern allows to distinguish "type" of a class within a module (for example, Block, Model) * Returns array( => ) * * @param string $subTypePattern * @return array */ public static function collectModuleClasses($subTypePattern = '[A-Za-z]+') { $componentRegistrar = new ComponentRegistrar(); $result = []; foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $modulePath) { $pattern = '/^' . preg_quote($modulePath, '/') . '\/(' . $subTypePattern . '\/.+)\.php$/'; foreach (Files::init()->getFiles([$modulePath], '*.php') as $file) { if (preg_match($pattern, $file)) { $partialFileName = substr($file, strlen($modulePath) + 1); $partialFileName = substr($partialFileName, 0, strlen($partialFileName) - strlen('.php')); $partialClassName = str_replace('/', '\\', $partialFileName); $className = str_replace('_', '\\', $moduleName) . '\\' . $partialClassName; $result[$className] = $moduleName; } } } return $result; } /** * Fetch virtual class declarations from DI configs * * @return array */ public static function getVirtualClasses() { if (!empty(self::$_virtualClasses)) { return self::$_virtualClasses; } $configFiles = Files::init()->getDiConfigs(); foreach ($configFiles as $fileName) { $configDom = new \DOMDocument(); $configDom->load($fileName); $xPath = new \DOMXPath($configDom); $vTypes = $xPath->query('/config/virtualType'); /** @var \DOMNode $virtualType */ foreach ($vTypes as $virtualType) { $name = $virtualType->attributes->getNamedItem('name')->textContent; if (!$virtualType->attributes->getNamedItem('type')) { continue; } $type = $virtualType->attributes->getNamedItem('type')->textContent; self::$_virtualClasses[$name] = $type; } } return self::$_virtualClasses; } /** * Check if instance is virtual type * * @param $className string * @return bool */ public static function isVirtual($className) { //init virtual classes if necessary self::getVirtualClasses(); return array_key_exists($className, self::$_virtualClasses); } /** * Get real type name for virtual type * * @param string $className * @return string */ public static function resolveVirtualType($className) { if (false == self::isVirtual($className)) { return $className; } $resolvedName = self::$_virtualClasses[$className]; return self::resolveVirtualType($resolvedName); } /** * Check class is auto-generated * * @param string $className * @return bool */ public static function isAutogenerated($className) { if ( preg_match( '/.*\\\\[a-zA-Z0-9]{1,}(Factory|Proxy|SearchResults|DataBuilder|Extension|ExtensionInterface)$/', $className ) || preg_match('/Magento\\\\[\w]+\\\\(Test\\\\(Page|Fixture))\\\\/', $className) ) { return true; } return false; } /** * Scan contents as PHP-code and find class name occurrences * * @param string $contents * @param array &$classes * @return array */ public static function collectPhpCodeClasses($contents, &$classes = []) { self::getAllMatches( $contents, '/ # ::getModel ::getSingleton ::getResourceModel ::getResourceSingleton \:\:get(?:Resource)?(?:Model | Singleton)\(\s*[\'"]([^\'"]+)[\'"]\s*[\),] # addBlock createBlock getBlockSingleton | (?:addBlock | createBlock | getBlockSingleton)\(\s*[\'"]([^\'"]+)[\'"]\s*[\),] # various methods, first argument | \->(?:initReport | setEntityModelClass | setAttributeModel | setBackendModel | setFrontendModel | setSourceModel | setModel )\(\s*[\'"]([^\'"]+)[\'"]\s*[\),] # various methods, second argument | \->add(?:ProductConfigurationHelper | OptionsRenderCfg)\(.+,\s*[\'"]([^\'"]+)[\'"]\s*[\),] # models in install or setup | [\'"](?:resource_model | attribute_model | entity_model | entity_attribute_collection | source | backend | frontend | input_renderer | frontend_input_renderer )[\'"]\s*=>\s*[\'"]([^\'"]+)[\'"] # misc | function\s_getCollectionClass\(\)\s+{\s+return\s+[\'"]([a-z\d_\/]+)[\'"] | (?:_parentResourceModelName | _checkoutType | _apiType)\s*=\s*\'([a-z\d_\/]+)\' | \'renderer\'\s*=>\s*\'([a-z\d_\/]+)\' | protected\s+\$_(?:form|info|backendForm|iframe)BlockType\s*=\s*[\'"]([^\'"]+)[\'"] /Uix', $classes ); // check ->_init | parent::_init $skipForInit = implode( '|', [ 'id', '[\w\d_]+_id', 'pk', 'code', 'status', 'serial_number', 'entity_pk_value', 'currency_code', 'unique_key' ] ); self::getAllMatches( $contents, '/ (?:parent\:\: | \->)_init\(\s*[\'"]([^\'"]+)[\'"]\s*\) | (?:parent\:\: | \->)_init\(\s*[\'"]([^\'"]+)[\'"]\s*,\s*[\'"]((?!(' . $skipForInit . '))[^\'"]+)[\'"]\s*\) /Uix', $classes ); return $classes; } /** * Retrieve module name by class * * @param string $class * @return string */ public static function getClassModuleName($class) { $parts = explode('\\', trim($class, '\\')); return $parts[0] . '_' . $parts[1]; } }