Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow modules to live outside of app/code directory #1206

Merged
merged 8 commits into from
Jun 19, 2015
6 changes: 6 additions & 0 deletions app/etc/di.xml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<preference for="Magento\Framework\Config\CacheInterface" type="Magento\Framework\App\Cache\Type\Config" />
<preference for="Magento\Framework\Config\ValidationStateInterface" type="Magento\Framework\App\Arguments\ValidationState" />
<preference for="Magento\Framework\Module\ModuleListInterface" type="Magento\Framework\Module\ModuleList" />
<preference for="Magento\Framework\Module\ModuleRegistryInterface" type="Magento\Framework\Module\Registrar" />
<preference for="Magento\Framework\Event\ConfigInterface" type="Magento\Framework\Event\Config" />
<preference for="Magento\Framework\Event\InvokerInterface" type="Magento\Framework\Event\Invoker\InvokerDefault" />
<preference for="Magento\Framework\Interception\PluginListInterface" type="Magento\Framework\Interception\PluginList\PluginList" />
Expand Down Expand Up @@ -1075,6 +1076,11 @@
<argument name="overriddenBaseFiles" xsi:type="object">lessFileOverriddenBase</argument>
</arguments>
</type>
<type name="Magento\Framework\Module\ModuleList\Loader">
<arguments>
<argument name="filesystemDriver" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
</arguments>
</type>
<type name="Magento\Framework\Module\Setup\MigrationData">
<arguments>
<argument name="data" xsi:type="array">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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"
30 changes: 23 additions & 7 deletions lib/internal/Magento/Framework/Module/Dir.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
}

/**
Expand All @@ -45,16 +59,18 @@ 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.");
}
$path .= '/' . $type;
}

$result = $this->_modulesDirectory->getAbsolutePath($path);

return $result;
return $path;
}
}
54 changes: 48 additions & 6 deletions lib/internal/Magento/Framework/Module/ModuleList/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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) {
Expand All @@ -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 <[email protected]>
*/
private function getModuleConfigs()
{
$modulesDir = $this->filesystem->getDirectoryRead(DirectoryList::MODULES);
foreach ($modulesDir->search('*/*/etc/module.xml') as $filePath) {
yield $modulesDir->readFile($filePath);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, that's the first usage of generators in Magento 2 :)

}

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
*
Expand Down
27 changes: 27 additions & 0 deletions lib/internal/Magento/Framework/Module/ModuleRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\Module;

/**
* @author Josh Di Fabio <[email protected]>
*/
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);
}
48 changes: 48 additions & 0 deletions lib/internal/Magento/Framework/Module/Registrar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\Module;

/**
* Provides ability to statically register modules which do not reside in the modules directory
*
* @author Josh Di Fabio <[email protected]>
*/
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;
}
}
58 changes: 53 additions & 5 deletions lib/internal/Magento/Framework/Module/Test/Unit/DirTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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()
Expand All @@ -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(
Expand All @@ -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'));
}

Expand All @@ -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');
}
}
Loading