Loader.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Module\ModuleList;
  7. use Magento\Framework\Module\Declaration\Converter\Dom;
  8. use Magento\Framework\Xml\Parser;
  9. use Magento\Framework\Component\ComponentRegistrarInterface;
  10. use Magento\Framework\Component\ComponentRegistrar;
  11. use Magento\Framework\Filesystem\DriverInterface;
  12. /**
  13. * Loader of module list information from the filesystem
  14. *
  15. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  16. */
  17. class Loader
  18. {
  19. /**
  20. * Converter of XML-files to associative arrays (specific to module.xml file format)
  21. *
  22. * @var Dom
  23. */
  24. private $converter;
  25. /**
  26. * Parser
  27. *
  28. * @var \Magento\Framework\Xml\Parser
  29. */
  30. private $parser;
  31. /**
  32. * Module registry
  33. *
  34. * @var ComponentRegistrarInterface
  35. */
  36. private $moduleRegistry;
  37. /**
  38. * Filesystem driver to allow reading of module.xml files which live outside of app/code
  39. *
  40. * @var DriverInterface
  41. */
  42. private $filesystemDriver;
  43. /**
  44. * Constructor
  45. *
  46. * @param Dom $converter
  47. * @param Parser $parser
  48. * @param ComponentRegistrarInterface $moduleRegistry
  49. * @param DriverInterface $filesystemDriver
  50. */
  51. public function __construct(
  52. Dom $converter,
  53. Parser $parser,
  54. ComponentRegistrarInterface $moduleRegistry,
  55. DriverInterface $filesystemDriver
  56. ) {
  57. $this->converter = $converter;
  58. $this->parser = $parser;
  59. $this->parser->initErrorHandler();
  60. $this->moduleRegistry = $moduleRegistry;
  61. $this->filesystemDriver = $filesystemDriver;
  62. }
  63. /**
  64. * Loads the full module list information. Excludes modules specified in $exclude.
  65. *
  66. * @param array $exclude
  67. * @throws \Magento\Framework\Exception\LocalizedException
  68. * @return array
  69. */
  70. public function load(array $exclude = [])
  71. {
  72. $result = [];
  73. foreach ($this->getModuleConfigs() as list($file, $contents)) {
  74. try {
  75. $this->parser->loadXML($contents);
  76. } catch (\Magento\Framework\Exception\LocalizedException $e) {
  77. throw new \Magento\Framework\Exception\LocalizedException(
  78. new \Magento\Framework\Phrase(
  79. 'Invalid Document: %1%2 Error: %3',
  80. [$file, PHP_EOL, $e->getMessage()]
  81. ),
  82. $e
  83. );
  84. }
  85. $data = $this->converter->convert($this->parser->getDom());
  86. $name = key($data);
  87. if (!in_array($name, $exclude)) {
  88. $result[$name] = $data[$name];
  89. }
  90. }
  91. return $this->sortBySequence($result);
  92. }
  93. /**
  94. * Returns module config data and a path to the module.xml file.
  95. *
  96. * Example of data returned by generator:
  97. * <code>
  98. * [ 'vendor/module/etc/module.xml', '<xml>contents</xml>' ]
  99. * </code>
  100. *
  101. * @return \Traversable
  102. *
  103. * @author Josh Di Fabio <joshdifabio@gmail.com>
  104. */
  105. private function getModuleConfigs()
  106. {
  107. $modulePaths = $this->moduleRegistry->getPaths(ComponentRegistrar::MODULE);
  108. foreach ($modulePaths as $modulePath) {
  109. $filePath = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, "$modulePath/etc/module.xml");
  110. yield [$filePath, $this->filesystemDriver->fileGetContents($filePath)];
  111. }
  112. }
  113. /**
  114. * Sort the list of modules using "sequence" key in meta-information
  115. *
  116. * @param array $origList
  117. * @return array
  118. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  119. */
  120. private function sortBySequence($origList)
  121. {
  122. ksort($origList);
  123. $expanded = [];
  124. foreach ($origList as $moduleName => $value) {
  125. $expanded[] = [
  126. 'name' => $moduleName,
  127. 'sequence' => $this->expandSequence($origList, $moduleName),
  128. ];
  129. }
  130. // Use "bubble sorting" because usort does not check each pair of elements and in this case it is important
  131. $total = count($expanded);
  132. for ($i = 0; $i < $total - 1; $i++) {
  133. for ($j = $i; $j < $total; $j++) {
  134. if (in_array($expanded[$j]['name'], $expanded[$i]['sequence'])) {
  135. $temp = $expanded[$i];
  136. $expanded[$i] = $expanded[$j];
  137. $expanded[$j] = $temp;
  138. }
  139. }
  140. }
  141. $result = [];
  142. foreach ($expanded as $pair) {
  143. $result[$pair['name']] = $origList[$pair['name']];
  144. }
  145. return $result;
  146. }
  147. /**
  148. * Accumulate information about all transitive "sequence" references
  149. *
  150. * @param array $list
  151. * @param string $name
  152. * @param array $accumulated
  153. * @return array
  154. * @throws \Exception
  155. */
  156. private function expandSequence($list, $name, $accumulated = [])
  157. {
  158. $accumulated[] = $name;
  159. $result = $list[$name]['sequence'];
  160. foreach ($result as $relatedName) {
  161. if (in_array($relatedName, $accumulated)) {
  162. throw new \Exception("Circular sequence reference from '{$name}' to '{$relatedName}'.");
  163. }
  164. if (!isset($list[$relatedName])) {
  165. continue;
  166. }
  167. $relatedResult = $this->expandSequence($list, $relatedName, $accumulated);
  168. $result = array_unique(array_merge($result, $relatedResult));
  169. }
  170. return $result;
  171. }
  172. }