converter = $converter; $this->parser = $parser; $this->parser->initErrorHandler(); $this->moduleRegistry = $moduleRegistry; $this->filesystemDriver = $filesystemDriver; } /** * Loads the full module list information. Excludes modules specified in $exclude. * * @param array $exclude * @throws \Magento\Framework\Exception\LocalizedException * @return array */ public function load(array $exclude = []) { $result = []; foreach ($this->getModuleConfigs() as list($file, $contents)) { try { $this->parser->loadXML($contents); } catch (\Magento\Framework\Exception\LocalizedException $e) { throw new \Magento\Framework\Exception\LocalizedException( new \Magento\Framework\Phrase( 'Invalid Document: %1%2 Error: %3', [$file, PHP_EOL, $e->getMessage()] ), $e ); } $data = $this->converter->convert($this->parser->getDom()); $name = key($data); if (!in_array($name, $exclude)) { $result[$name] = $data[$name]; } } return $this->sortBySequence($result); } /** * Returns module config data and a path to the module.xml file. * * Example of data returned by generator: * * [ 'vendor/module/etc/module.xml', 'contents' ] * * * @return \Traversable * * @author Josh Di Fabio */ private function getModuleConfigs() { $modulePaths = $this->moduleRegistry->getPaths(ComponentRegistrar::MODULE); foreach ($modulePaths as $modulePath) { $filePath = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, "$modulePath/etc/module.xml"); yield [$filePath, $this->filesystemDriver->fileGetContents($filePath)]; } } /** * Sort the list of modules using "sequence" key in meta-information * * @param array $origList * @return array * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ private function sortBySequence($origList) { ksort($origList); $expanded = []; foreach ($origList as $moduleName => $value) { $expanded[] = [ 'name' => $moduleName, 'sequence' => $this->expandSequence($origList, $moduleName), ]; } // Use "bubble sorting" because usort does not check each pair of elements and in this case it is important $total = count($expanded); for ($i = 0; $i < $total - 1; $i++) { for ($j = $i; $j < $total; $j++) { if (in_array($expanded[$j]['name'], $expanded[$i]['sequence'])) { $temp = $expanded[$i]; $expanded[$i] = $expanded[$j]; $expanded[$j] = $temp; } } } $result = []; foreach ($expanded as $pair) { $result[$pair['name']] = $origList[$pair['name']]; } return $result; } /** * Accumulate information about all transitive "sequence" references * * @param array $list * @param string $name * @param array $accumulated * @return array * @throws \Exception */ private function expandSequence($list, $name, $accumulated = []) { $accumulated[] = $name; $result = $list[$name]['sequence']; foreach ($result as $relatedName) { if (in_array($relatedName, $accumulated)) { throw new \Exception("Circular sequence reference from '{$name}' to '{$relatedName}'."); } if (!isset($list[$relatedName])) { continue; } $relatedResult = $this->expandSequence($list, $relatedName, $accumulated); $result = array_unique(array_merge($result, $relatedResult)); } return $result; } }