diff --git a/app/etc/di.xml b/app/etc/di.xml
old mode 100755
new mode 100644
index 00eb93bd1cc5f..4bdf8648c430d
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -44,6 +44,7 @@
+
@@ -1075,6 +1076,11 @@
lessFileOverriddenBase
+
+
+ Magento\Framework\Filesystem\Driver\File
+
+
diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/circular.csv b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/circular.csv
index 6d00c9f481554..34e69204e8e9f 100644
--- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/circular.csv
+++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/circular.csv
@@ -2,8 +2,8 @@
"","2"
"Circular dependencies for each module:",""
-"magento/module-a","1"
-"magento/module-a->magento/module-b->magento/module-a"
-
"magento/module-b","1"
"magento/module-b->magento/module-a->magento/module-b"
+
+"magento/module-a","1"
+"magento/module-a->magento/module-b->magento/module-a"
diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/framework.csv b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/framework.csv
index b6abb23cd683f..5f3acfe70bd27 100644
--- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/framework.csv
+++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/framework.csv
@@ -2,8 +2,8 @@
"","2"
"Dependencies for each module:",""
-"Magento\A","1"
+"Magento\B","1"
" -- Magento\Framework","1"
-"Magento\B","1"
+"Magento\A","1"
" -- Magento\Framework","1"
diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/modules.csv b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/modules.csv
index 41deca9466eca..0bc98654f4f5f 100644
--- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/modules.csv
+++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/expected/modules.csv
@@ -2,8 +2,8 @@
"Total number of dependencies","2","2","0"
"Dependencies for each module:","All","Hard","Soft"
-"magento/module-a","1","1","0"
-" -- magento/module-b","","1","0"
-
"magento/module-b","1","1","0"
" -- magento/module-a","","1","0"
+
+"magento/module-a","1","1","0"
+" -- magento/module-b","","1","0"
diff --git a/lib/internal/Magento/Framework/Module/Dir.php b/lib/internal/Magento/Framework/Module/Dir.php
index 749e443fe7c10..4d3b811ea8e60 100644
--- a/lib/internal/Magento/Framework/Module/Dir.php
+++ b/lib/internal/Magento/Framework/Module/Dir.php
@@ -10,6 +10,8 @@
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\ReadInterface;
+use Magento\Framework\Stdlib\String as StringHelper;
+use Magento\Framework\Module\ModuleRegistryInterface;
class Dir
{
@@ -25,14 +27,26 @@ class Dir
*/
protected $_string;
+ /**
+ * Module registry
+ *
+ * @var ModuleRegistryInterface
+ */
+ private $moduleRegistry;
+
/**
* @param Filesystem $filesystem
- * @param \Magento\Framework\Stdlib\String $string
+ * @param StringHelper $string
+ * @param ModuleRegistryInterface $moduleRegistry
*/
- public function __construct(Filesystem $filesystem, \Magento\Framework\Stdlib\String $string)
- {
+ public function __construct(
+ Filesystem $filesystem,
+ StringHelper $string,
+ ModuleRegistryInterface $moduleRegistry
+ ) {
$this->_modulesDirectory = $filesystem->getDirectoryRead(DirectoryList::MODULES);
$this->_string = $string;
+ $this->moduleRegistry = $moduleRegistry;
}
/**
@@ -45,7 +59,11 @@ public function __construct(Filesystem $filesystem, \Magento\Framework\Stdlib\St
*/
public function getDir($moduleName, $type = '')
{
- $path = $this->_string->upperCaseWords($moduleName, '_', '/');
+ if (null === $path = $this->moduleRegistry->getModulePath($moduleName)) {
+ $relativePath = $this->_string->upperCaseWords($moduleName, '_', '/');
+ $path = $this->_modulesDirectory->getAbsolutePath($relativePath);
+ }
+
if ($type) {
if (!in_array($type, ['etc', 'i18n', 'view', 'Controller'])) {
throw new \InvalidArgumentException("Directory type '{$type}' is not recognized.");
@@ -53,8 +71,6 @@ public function getDir($moduleName, $type = '')
$path .= '/' . $type;
}
- $result = $this->_modulesDirectory->getAbsolutePath($path);
-
- return $result;
+ return $path;
}
}
diff --git a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php
index 08660d98bb6eb..8708d86ba3006 100644
--- a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php
+++ b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php
@@ -10,6 +10,8 @@
use Magento\Framework\Filesystem;
use Magento\Framework\Module\Declaration\Converter\Dom;
use Magento\Framework\Xml\Parser;
+use Magento\Framework\Module\ModuleRegistryInterface;
+use Magento\Framework\Filesystem\DriverInterface;
/**
* Loader of module list information from the filesystem
@@ -37,19 +39,42 @@ class Loader
*/
private $parser;
+ /**
+ * Module registry
+ *
+ * @var ModuleRegistryInterface
+ */
+ private $moduleRegistry;
+
+ /**
+ * Filesystem driver to allow reading of module.xml files which live outside of app/code
+ *
+ * @var DriverInterface
+ */
+ private $filesystemDriver;
+
/**
* Constructor
*
* @param Filesystem $filesystem
* @param Dom $converter
* @param Parser $parser
+ * @param ModuleRegistryInterface $moduleRegistry
+ * @param DriverInterface $filesystemDriver
*/
- public function __construct(Filesystem $filesystem, Dom $converter, Parser $parser)
- {
+ public function __construct(
+ Filesystem $filesystem,
+ Dom $converter,
+ Parser $parser,
+ ModuleRegistryInterface $moduleRegistry,
+ DriverInterface $filesystemDriver
+ ) {
$this->filesystem = $filesystem;
$this->converter = $converter;
$this->parser = $parser;
$this->parser->initErrorHandler();
+ $this->moduleRegistry = $moduleRegistry;
+ $this->filesystemDriver = $filesystemDriver;
}
/**
@@ -61,10 +86,7 @@ public function __construct(Filesystem $filesystem, Dom $converter, Parser $pars
public function load()
{
$result = [];
- $dir = $this->filesystem->getDirectoryRead(DirectoryList::MODULES);
- foreach ($dir->search('*/*/etc/module.xml') as $file) {
- $contents = $dir->readFile($file);
-
+ foreach ($this->getModuleConfigs() as $contents) {
try {
$this->parser->loadXML($contents);
} catch (\Magento\Framework\Exception\LocalizedException $e) {
@@ -84,6 +106,26 @@ public function load()
return $this->sortBySequence($result);
}
+ /**
+ * Returns a traversable yielding content of all module.xml files
+ *
+ * @return \Traversable
+ *
+ * @author Josh Di Fabio
+ */
+ private function getModuleConfigs()
+ {
+ $modulesDir = $this->filesystem->getDirectoryRead(DirectoryList::MODULES);
+ foreach ($modulesDir->search('*/*/etc/module.xml') as $filePath) {
+ yield $modulesDir->readFile($filePath);
+ }
+
+ foreach ($this->moduleRegistry->getModulePaths() as $modulePath) {
+ $filePath = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, "$modulePath/etc/module.xml");
+ yield $this->filesystemDriver->fileGetContents($filePath);
+ }
+ }
+
/**
* Sort the list of modules using "sequence" key in meta-information
*
diff --git a/lib/internal/Magento/Framework/Module/ModuleRegistryInterface.php b/lib/internal/Magento/Framework/Module/ModuleRegistryInterface.php
new file mode 100644
index 0000000000000..e589057bf0a12
--- /dev/null
+++ b/lib/internal/Magento/Framework/Module/ModuleRegistryInterface.php
@@ -0,0 +1,27 @@
+
+ */
+interface ModuleRegistryInterface
+{
+ /**
+ * Get list of Magento module paths
+ *
+ * Returns an array where key is fully-qualified module name and value is absolute path to module
+ *
+ * @return array
+ */
+ public function getModulePaths();
+
+ /**
+ * @param string $moduleName
+ * @return null|string
+ */
+ public function getModulePath($moduleName);
+}
diff --git a/lib/internal/Magento/Framework/Module/Registrar.php b/lib/internal/Magento/Framework/Module/Registrar.php
new file mode 100644
index 0000000000000..4494f3fc030d0
--- /dev/null
+++ b/lib/internal/Magento/Framework/Module/Registrar.php
@@ -0,0 +1,48 @@
+
+ */
+class Registrar implements ModuleRegistryInterface
+{
+ /**
+ * Paths to modules
+ *
+ * @var string[]
+ */
+ private static $modulePaths = [];
+
+ /**
+ * Sets the location of a module. Necessary for modules which do not reside in modules directory
+ *
+ * @param string $moduleName Fully-qualified module name
+ * @param string $path Absolute file path to the module
+ */
+ public static function registerModule($moduleName, $path)
+ {
+ self::$modulePaths[$moduleName] = $path;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getModulePaths()
+ {
+ return self::$modulePaths;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getModulePath($moduleName)
+ {
+ return isset(self::$modulePaths[$moduleName]) ? self::$modulePaths[$moduleName] : null;
+ }
+}
diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/DirTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/DirTest.php
index 8b25ec2523c07..616aae5a11007 100644
--- a/lib/internal/Magento/Framework/Module/Test/Unit/DirTest.php
+++ b/lib/internal/Magento/Framework/Module/Test/Unit/DirTest.php
@@ -27,6 +27,11 @@ class DirTest extends \PHPUnit_Framework_TestCase
*/
protected $directoryMock;
+ /**
+ * @var \Magento\Framework\Module\ModuleRegistryInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $moduleRegistryMock;
+
protected function setUp()
{
$this->filesystemMock = $this->getMock('Magento\Framework\Filesystem', [], [], '', false, false);
@@ -39,8 +44,14 @@ protected function setUp()
false
);
$this->_stringMock = $this->getMock('Magento\Framework\Stdlib\String', [], [], '', false, false);
-
- $this->_stringMock->expects($this->once())->method('upperCaseWords')->will($this->returnValue('Test/Module'));
+ $this->moduleRegistryMock = $this->getMock(
+ 'Magento\Framework\Module\ModuleRegistryInterface',
+ [],
+ [],
+ '',
+ false,
+ false
+ );
$this->filesystemMock->expects(
$this->once()
@@ -50,11 +61,27 @@ protected function setUp()
$this->returnValue($this->directoryMock)
);
- $this->_model = new \Magento\Framework\Module\Dir($this->filesystemMock, $this->_stringMock);
+ $this->_model = new \Magento\Framework\Module\Dir(
+ $this->filesystemMock,
+ $this->_stringMock,
+ $this->moduleRegistryMock
+ );
}
public function testGetDirModuleRoot()
{
+ $this->moduleRegistryMock->expects(
+ $this->once()
+ )->method(
+ 'getModulePath'
+ )->with(
+ 'Test_Module'
+ )->will(
+ $this->returnValue(null)
+ );
+
+ $this->_stringMock->expects($this->once())->method('upperCaseWords')->will($this->returnValue('Test/Module'));
+
$this->directoryMock->expects(
$this->once()
)->method(
@@ -64,20 +91,39 @@ public function testGetDirModuleRoot()
)->will(
$this->returnValue('/Test/Module')
);
+
$this->assertEquals('/Test/Module', $this->_model->getDir('Test_Module'));
}
+ public function testGetDirModuleRootFromResolver()
+ {
+ $this->moduleRegistryMock->expects(
+ $this->once()
+ )->method(
+ 'getModulePath'
+ )->with(
+ 'Test_Module2'
+ )->will(
+ $this->returnValue('/path/to/module')
+ );
+
+ $this->assertEquals('/path/to/module', $this->_model->getDir('Test_Module2'));
+ }
+
public function testGetDirModuleSubDir()
{
+ $this->_stringMock->expects($this->once())->method('upperCaseWords')->will($this->returnValue('Test/Module'));
+
$this->directoryMock->expects(
$this->once()
)->method(
'getAbsolutePath'
)->with(
- 'Test/Module/etc'
+ 'Test/Module'
)->will(
- $this->returnValue('/Test/Module/etc')
+ $this->returnValue('/Test/Module')
);
+
$this->assertEquals('/Test/Module/etc', $this->_model->getDir('Test_Module', 'etc'));
}
@@ -87,6 +133,8 @@ public function testGetDirModuleSubDir()
*/
public function testGetDirModuleSubDirUnknown()
{
+ $this->_stringMock->expects($this->once())->method('upperCaseWords')->will($this->returnValue('Test/Module'));
+
$this->_model->getDir('Test_Module', 'unknown');
}
}
diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php
index 5185851d78bb5..3e232f7b97cfe 100644
--- a/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php
+++ b/lib/internal/Magento/Framework/Module/Test/Unit/ModuleList/LoaderTest.php
@@ -40,6 +40,21 @@ class LoaderTest extends \PHPUnit_Framework_TestCase
*/
private $parser;
+ /*
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $registry;
+
+ /*
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $driver;
+
+ /**
+ * @var \Magento\Framework\Module\ModuleList\Loader
+ */
+ private $loader;
+
protected function setUp()
{
$this->filesystem = $this->getMock('Magento\Framework\Filesystem', [], [], '', false);
@@ -50,34 +65,44 @@ protected function setUp()
->willReturn($this->dir);
$this->converter = $this->getMock('Magento\Framework\Module\Declaration\Converter\Dom', [], [], '', false);
$this->parser = $this->getMock('Magento\Framework\Xml\Parser', [], [], '', false);
+ $this->parser->expects($this->once())->method('initErrorHandler');
+ $this->registry = $this->getMock('Magento\Framework\Module\ModuleRegistryInterface', [], [], '', false, false);
+ $this->driver = $this->getMock('Magento\Framework\Filesystem\DriverInterface', [], [], '', false, false);
+ $this->loader = new Loader($this->filesystem, $this->converter, $this->parser, $this->registry, $this->driver);
}
public function testLoad()
{
- $fixture = [
+ $fixtures = [
'a' => ['name' => 'a', 'sequence' => []], // a is on its own
- 'b' => ['name' => 'b', 'sequence' => ['c']], // b is after c
- 'c' => ['name' => 'c', 'sequence' => ['a']], // c is after a
- // so expected sequence is a -> c -> b
+ 'b' => ['name' => 'b', 'sequence' => ['d']], // b is after c
+ 'c' => ['name' => 'c', 'sequence' => ['e']], // c is after e
+ 'd' => ['name' => 'd', 'sequence' => ['c']], // d is after c
+ 'e' => ['name' => 'e', 'sequence' => ['a']], // e is after a
+ // so expected sequence is a -> e -> c -> d -> b
];
$this->dir->expects($this->once())->method('search')->willReturn(['a', 'b', 'c']);
+ $this->registry->expects($this->once())->method('getModulePaths')->willReturn(['/path/to/d', '/path/to/e']);
$this->dir->expects($this->exactly(3))->method('readFile')->will($this->returnValueMap([
['a', null, null, self::$sampleXml],
['b', null, null, self::$sampleXml],
['c', null, null, self::$sampleXml],
]));
- $this->converter->expects($this->at(0))->method('convert')->willReturn(['a' => $fixture['a']]);
- $this->converter->expects($this->at(1))->method('convert')->willReturn(['b' => $fixture['b']]);
- $this->converter->expects($this->at(2))->method('convert')->willReturn(['c' => $fixture['c']]);
- $this->parser->expects($this->once())->method('initErrorHandler');
+ $this->driver->expects($this->exactly(2))->method('fileGetContents')->will($this->returnValueMap([
+ ['/path/to/d', null, null, self::$sampleXml],
+ ['/path/to/e', null, null, self::$sampleXml],
+ ]));
+ $index = 0;
+ foreach ($fixtures as $name => $fixture) {
+ $this->converter->expects($this->at($index++))->method('convert')->willReturn([$name => $fixture]);
+ }
$this->parser->expects($this->atLeastOnce())->method('loadXML');
$this->parser->expects($this->atLeastOnce())->method('getDom');
- $object = new Loader($this->filesystem, $this->converter, $this->parser);
- $result = $object->load();
- $this->assertSame(['a', 'c', 'b'], array_keys($result));
- $this->assertSame($fixture['a'], $result['a']);
- $this->assertSame($fixture['b'], $result['b']);
- $this->assertSame($fixture['c'], $result['c']);
+ $result = $this->loader->load();
+ $this->assertSame(['a', 'e', 'c', 'd', 'b'], array_keys($result));
+ foreach ($fixtures as $name => $fixture) {
+ $this->assertSame($fixture, $result[$name]);
+ }
}
/**
@@ -97,7 +122,7 @@ public function testLoadCircular()
]));
$this->converter->expects($this->at(0))->method('convert')->willReturn(['a' => $fixture['a']]);
$this->converter->expects($this->at(1))->method('convert')->willReturn(['b' => $fixture['b']]);
- $object = new Loader($this->filesystem, $this->converter, $this->parser);
- $object->load();
+ $this->registry->expects($this->once())->method('getModulePaths')->willReturn([]);
+ $this->loader->load();
}
}
diff --git a/setup/config/di.config.php b/setup/config/di.config.php
index 8d0f258ba8287..30e6af237e912 100644
--- a/setup/config/di.config.php
+++ b/setup/config/di.config.php
@@ -28,6 +28,8 @@
'Zend\ServiceManager\ServiceLocatorInterface' => 'ServiceManager',
'Magento\Framework\DB\LoggerInterface' => 'Magento\Framework\DB\Logger\Null',
'Magento\Framework\Locale\ConfigInterface' => 'Magento\Framework\Locale\Config',
+ 'Magento\Framework\Module\ModuleRegistryInterface' => 'Magento\Framework\Module\Registrar',
+ 'Magento\Framework\Filesystem\DriverInterface' => 'Magento\Framework\Filesystem\Driver\File',
],
],
],