Skip to content

Fall back to parent directory if composer.json not found in module directory #8990

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

Merged
merged 10 commits into from
Aug 2, 2017
64 changes: 43 additions & 21 deletions app/code/Magento/SampleData/Model/Dependency.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
*/
namespace Magento\SampleData\Model;

use Magento\Framework\App\ObjectManager;
use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Component\ComponentRegistrarInterface;
use Magento\Framework\Composer\ComposerInformation;
use Magento\Framework\Filesystem;
use Magento\Framework\Config\Composer\Package;
use Magento\Framework\Config\Composer\PackageFactory;
use Magento\Framework\Filesystem;

/**
* Sample Data dependency
Expand All @@ -26,43 +28,54 @@ class Dependency
*/
protected $composerInformation;

/**
* @var Filesystem
*/
private $filesystem;

/**
* @var PackageFactory
*/
private $packageFactory;

/**
* @var ComponentRegistrar
* @var ComponentRegistrarInterface
*/
private $componentRegistrar;

/**
* @var Filesystem\Directory\ReadInterfaceFactory
*/
private $directoryReadFactory;

//@codingStandardsIgnoreStart
/**
* @param ComposerInformation $composerInformation
* @param Filesystem $filesystem
* @param PackageFactory $packageFactory
* @param ComponentRegistrar $componentRegistrar
* @param ComponentRegistrarInterface $componentRegistrar
* @param Filesystem\Directory\ReadInterfaceFactory $directoryReadFactory
* @throws \RuntimeException
*/
public function __construct(
ComposerInformation $composerInformation,
// $filesystem kept for BC
Filesystem $filesystem,
PackageFactory $packageFactory,
ComponentRegistrar $componentRegistrar
ComponentRegistrarInterface $componentRegistrar,
// $directoryReadFactory optional for BC
Filesystem\Directory\ReadInterfaceFactory $directoryReadFactory = null
) {
$this->composerInformation = $composerInformation;
$this->filesystem = $filesystem;
$this->packageFactory = $packageFactory;
$this->componentRegistrar = $componentRegistrar;
if ($directoryReadFactory === null) {
$directoryReadFactory = ObjectManager::getInstance()->get(Filesystem\Directory\ReadInterfaceFactory::class);
}
$this->directoryReadFactory = $directoryReadFactory;
}
//@codingStandardsIgnoreEnd

/**
* Retrieve list of sample data packages from suggests
*
* @return array
* @throws \Magento\Framework\Exception\FileSystemException
*/
public function getSampleDataPackages()
{
Expand All @@ -81,19 +94,13 @@ public function getSampleDataPackages()
* Retrieve suggested sample data packages from modules composer.json
*
* @return array
* @throws \Magento\Framework\Exception\FileSystemException
*/
protected function getSuggestsFromModules()
{
$suggests = [];
foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
$file = $moduleDir . '/composer.json';

if (!file_exists($file) || !is_readable($file)) {
continue;
}

/** @var Package $package */
$package = $this->getModuleComposerPackage($file);
$package = $this->getModuleComposerPackage($moduleDir);
$suggest = json_decode(json_encode($package->get('suggest')), true);
if (!empty($suggest)) {
$suggests += $suggest;
Expand All @@ -105,11 +112,26 @@ protected function getSuggestsFromModules()
/**
* Load package
*
* @param string $file
* @param string $moduleDir
* @return Package
* @throws \Magento\Framework\Exception\FileSystemException
*/
protected function getModuleComposerPackage($file)
private function getModuleComposerPackage($moduleDir)
{
return $this->packageFactory->create(['json' => json_decode(file_get_contents($file))]);
/*
* Also look in parent directory of registered module directory to allow modules to follow the pds/skeleton
* standard and have their source code in a "src" subdirectory of the repository
*
* see: https://github.com/php-pds/skeleton
*/
foreach ([$moduleDir, $moduleDir . DIRECTORY_SEPARATOR . '..'] as $dir) {
/** @var Filesystem\Directory\ReadInterface $directory */
$directory = $this->directoryReadFactory->create(['path' => $dir]);
if ($directory->isExist('composer.json') && $directory->isReadable('composer.json')) {
/** @var Package $package */
return $this->packageFactory->create(['json' => json_decode($directory->readFile('composer.json'))]);
}
}
return $this->packageFactory->create(['json' => new \stdClass]);
}
}
189 changes: 189 additions & 0 deletions app/code/Magento/SampleData/Test/Unit/Model/DependencyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php
/**
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\SampleData\Test\Unit\Model;

use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Component\ComponentRegistrarInterface;
use Magento\Framework\Composer\ComposerInformation;
use Magento\Framework\Config\Composer\Package;
use Magento\Framework\Config\Composer\PackageFactory;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Filesystem;
use Magento\Framework\Phrase;
use Magento\SampleData\Model\Dependency;

class DependencyTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider dataPackagesFromComposerSuggest
* @param string[] $moduleDirectories
* @param callable $composerJsonGenerator
* @param string[] $suggestionsFromLockFile
* @param string[] $expectedPackages
*/
public function testPackagesFromComposerSuggest(
array $moduleDirectories,
callable $composerJsonGenerator,
array $suggestionsFromLockFile,
array $expectedPackages
) {
/** @var ComposerInformation|\PHPUnit_Framework_MockObject_MockObject $composerInformation */
$composerInformation = $this->getMockBuilder(ComposerInformation::class)
->disableOriginalConstructor()
->getMock();
$composerInformation->method('getSuggestedPackages')
->willReturn($suggestionsFromLockFile);

/** @var Filesystem|\PHPUnit_Framework_MockObject_MockObject $filesystem */
$filesystem = $this->getMockBuilder(Filesystem::class)->disableOriginalConstructor()->getMock();

/** @var PackageFactory|\PHPUnit_Framework_MockObject_MockObject $packageFactory */
$packageFactory = $this->getMockBuilder(PackageFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
$packageFactory->method('create')
->willReturnCallback(function ($args) {
return new Package($args['json']);
});

/** @var ComponentRegistrarInterface|\PHPUnit_Framework_MockObject_MockObject $componentRegistrar */
$componentRegistrar = $this->getMockBuilder(ComponentRegistrarInterface::class)
->getMockForAbstractClass();
$componentRegistrar->method('getPaths')
->with(ComponentRegistrar::MODULE)
->willReturn(
$moduleDirectories
);

$directoryReadFactory = $this->getMockBuilder(Filesystem\Directory\ReadInterfaceFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
$directoryReadFactory->method('create')
->willReturnMap($composerJsonGenerator($this));

$dependency = new Dependency(
$composerInformation,
$filesystem,
$packageFactory,
$componentRegistrar,
$directoryReadFactory
);
$this->assertEquals($expectedPackages, $dependency->getSampleDataPackages());
}
public static function dataPackagesFromComposerSuggest()
{
return [
[
'moduleDirectories' => [
'app/code/LocalModule',
'app/code/LocalModuleWithoutComposerJson',
'vendor/company/module',
'vendor/company2/module/src'
],
'composerJsonGenerator' => function (DependencyTest $test) {
return [
[
['path' => 'app/code/LocalModule'],
$test->stubComposerJsonReader(
[
'name' => 'local/module',
'suggest' => [
'local/module-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '0.1.0'
]
]
)
],
[
['path' => 'app/code/LocalModuleWithoutComposerJson'],
$test->stubFileNotFoundReader()
],
[
['path' => 'vendor/company/module'],
$test->stubComposerJsonReader(
[
'name' => 'company/module',
'suggest' => [
'company/module-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '1.0.0-beta'
]
]
)
],
[
['path' => 'vendor/company2/module/src/..'],
$test->stubComposerJsonReader(
[
'name' => 'company2/module',
'suggest' => [
'company2/module-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '1.10'
]
]
)
],
[
['path' => 'vendor/company2/module/src'],
$test->stubFileNotFoundReader()
],
[
['path' => 'vendor/company/module/..'],
$test->stubFileNotFoundReader()
],
[
['path' => 'app/code/LocalModuleWithoutComposerJson/..'],
$test->stubFileNotFoundReader()
],
[
['path' => 'app/code/LocalModule/..'],
$test->stubFileNotFoundReader()
],
];
},
'suggestions' => [
'magento/foo-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '100.0.0',
'thirdparty/bar-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '1.2.3',
'thirdparty/something-else' => 'Just a suggested package',
],
'expectedPackages' => [
'magento/foo-sample-data' => '100.0.0',
'thirdparty/bar-sample-data' => '1.2.3',
'local/module-sample-data' => '0.1.0',
'company/module-sample-data' => '1.0.0-beta',
'company2/module-sample-data' => '1.10',
]
]
];
}

public function stubComposerJsonReader(array $composerJsonContent)
{
$stub = $this->getMockBuilder(Filesystem\Directory\ReadInterface::class)
->getMockForAbstractClass();
$stub->method('isExist')
->with('composer.json')
->willReturn(true);
$stub->method('isReadable')
->with('composer.json')
->willReturn(true);
$stub->method('readFile')
->with('composer.json')
->willReturn(json_encode($composerJsonContent));
return $stub;
}

public function stubFileNotFoundReader()
{
$stub = $this->getMockBuilder(Filesystem\Directory\ReadInterface::class)
->getMockForAbstractClass();
$stub->method('isExist')
->with('composer.json')
->willReturn(false);
$stub->method('readFile')
->with('composer.json')
->willThrowException(new FileSystemException(new Phrase('File not found')));
return $stub;
}
}
6 changes: 6 additions & 0 deletions app/code/Magento/SampleData/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@
</argument>
</arguments>
</type>
<virtualType name="Magento\SampleData\Filesystem\Directory\Read" type="Magento\Framework\Filesystem\Directory\Read">
<arguments>
<argument name="driver" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
</arguments>
</virtualType>
<preference for="Magento\Framework\Filesystem\Directory\ReadInterface" type="Magento\SampleData\Filesystem\Directory\Read" />
</config>