Skip to content

Commit ff91068

Browse files
author
Oleksii Korshenko
authored
MAGETWO-70798: Fall back to parent directory if composer.json not found in module directory #8990
2 parents da064df + ec00c1e commit ff91068

File tree

3 files changed

+236
-22
lines changed

3 files changed

+236
-22
lines changed

app/code/Magento/SampleData/Model/Dependency.php

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
*/
66
namespace Magento\SampleData\Model;
77

8+
use Magento\Framework\App\ObjectManager;
89
use Magento\Framework\Component\ComponentRegistrar;
10+
use Magento\Framework\Component\ComponentRegistrarInterface;
911
use Magento\Framework\Composer\ComposerInformation;
10-
use Magento\Framework\Filesystem;
1112
use Magento\Framework\Config\Composer\Package;
1213
use Magento\Framework\Config\Composer\PackageFactory;
14+
use Magento\Framework\Filesystem;
15+
use Magento\Framework\Filesystem\Directory\ReadInterfaceFactory;
1316

1417
/**
1518
* Sample Data dependency
@@ -26,43 +29,50 @@ class Dependency
2629
*/
2730
protected $composerInformation;
2831

29-
/**
30-
* @var Filesystem
31-
*/
32-
private $filesystem;
33-
3432
/**
3533
* @var PackageFactory
3634
*/
3735
private $packageFactory;
3836

3937
/**
40-
* @var ComponentRegistrar
38+
* @var ComponentRegistrarInterface
4139
*/
4240
private $componentRegistrar;
4341

4442
/**
43+
* @var ReadInterfaceFactory
44+
*/
45+
private $directoryReadFactory;
46+
47+
/**
48+
* Initialize dependencies.
49+
*
4550
* @param ComposerInformation $composerInformation
46-
* @param Filesystem $filesystem
51+
* @param Filesystem $filesystem @deprecated 2.3.0 $directoryReadFactory is used instead
4752
* @param PackageFactory $packageFactory
48-
* @param ComponentRegistrar $componentRegistrar
53+
* @param ComponentRegistrarInterface $componentRegistrar
54+
* @param Filesystem\Directory\ReadInterfaceFactory|null $directoryReadFactory
55+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
4956
*/
5057
public function __construct(
5158
ComposerInformation $composerInformation,
5259
Filesystem $filesystem,
5360
PackageFactory $packageFactory,
54-
ComponentRegistrar $componentRegistrar
61+
ComponentRegistrarInterface $componentRegistrar,
62+
\Magento\Framework\Filesystem\Directory\ReadInterfaceFactory $directoryReadFactory = null
5563
) {
5664
$this->composerInformation = $composerInformation;
57-
$this->filesystem = $filesystem;
5865
$this->packageFactory = $packageFactory;
5966
$this->componentRegistrar = $componentRegistrar;
67+
$this->directoryReadFactory = $directoryReadFactory ?:
68+
ObjectManager::getInstance()->get(ReadInterfaceFactory::class);
6069
}
6170

6271
/**
6372
* Retrieve list of sample data packages from suggests
6473
*
6574
* @return array
75+
* @throws \Magento\Framework\Exception\FileSystemException
6676
*/
6777
public function getSampleDataPackages()
6878
{
@@ -81,19 +91,13 @@ public function getSampleDataPackages()
8191
* Retrieve suggested sample data packages from modules composer.json
8292
*
8393
* @return array
94+
* @throws \Magento\Framework\Exception\FileSystemException
8495
*/
8596
protected function getSuggestsFromModules()
8697
{
8798
$suggests = [];
8899
foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) {
89-
$file = $moduleDir . '/composer.json';
90-
91-
if (!file_exists($file) || !is_readable($file)) {
92-
continue;
93-
}
94-
95-
/** @var Package $package */
96-
$package = $this->getModuleComposerPackage($file);
100+
$package = $this->getModuleComposerPackage($moduleDir);
97101
$suggest = json_decode(json_encode($package->get('suggest')), true);
98102
if (!empty($suggest)) {
99103
$suggests += $suggest;
@@ -105,11 +109,26 @@ protected function getSuggestsFromModules()
105109
/**
106110
* Load package
107111
*
108-
* @param string $file
112+
* @param string $moduleDir
109113
* @return Package
114+
* @throws \Magento\Framework\Exception\FileSystemException
110115
*/
111-
protected function getModuleComposerPackage($file)
116+
private function getModuleComposerPackage($moduleDir)
112117
{
113-
return $this->packageFactory->create(['json' => json_decode(file_get_contents($file))]);
118+
/*
119+
* Also look in parent directory of registered module directory to allow modules to follow the pds/skeleton
120+
* standard and have their source code in a "src" subdirectory of the repository
121+
*
122+
* see: https://github.com/php-pds/skeleton
123+
*/
124+
foreach ([$moduleDir, $moduleDir . DIRECTORY_SEPARATOR . '..'] as $dir) {
125+
/** @var Filesystem\Directory\ReadInterface $directory */
126+
$directory = $this->directoryReadFactory->create(['path' => $dir]);
127+
if ($directory->isExist('composer.json') && $directory->isReadable('composer.json')) {
128+
/** @var Package $package */
129+
return $this->packageFactory->create(['json' => json_decode($directory->readFile('composer.json'))]);
130+
}
131+
}
132+
return $this->packageFactory->create(['json' => new \stdClass]);
114133
}
115134
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\SampleData\Test\Unit\Model;
7+
8+
use Magento\Framework\Component\ComponentRegistrar;
9+
use Magento\Framework\Component\ComponentRegistrarInterface;
10+
use Magento\Framework\Composer\ComposerInformation;
11+
use Magento\Framework\Config\Composer\Package;
12+
use Magento\Framework\Config\Composer\PackageFactory;
13+
use Magento\Framework\Exception\FileSystemException;
14+
use Magento\Framework\Filesystem;
15+
use Magento\Framework\Phrase;
16+
use Magento\SampleData\Model\Dependency;
17+
18+
class DependencyTest extends \PHPUnit_Framework_TestCase
19+
{
20+
/**
21+
* @dataProvider dataPackagesFromComposerSuggest
22+
* @param string[] $moduleDirectories
23+
* @param callable $composerJsonGenerator
24+
* @param string[] $suggestionsFromLockFile
25+
* @param string[] $expectedPackages
26+
*/
27+
public function testPackagesFromComposerSuggest(
28+
array $moduleDirectories,
29+
callable $composerJsonGenerator,
30+
array $suggestionsFromLockFile,
31+
array $expectedPackages
32+
) {
33+
/** @var ComposerInformation|\PHPUnit_Framework_MockObject_MockObject $composerInformation */
34+
$composerInformation = $this->getMockBuilder(ComposerInformation::class)
35+
->disableOriginalConstructor()
36+
->getMock();
37+
$composerInformation->method('getSuggestedPackages')
38+
->willReturn($suggestionsFromLockFile);
39+
40+
/** @var Filesystem|\PHPUnit_Framework_MockObject_MockObject $filesystem */
41+
$filesystem = $this->getMockBuilder(Filesystem::class)->disableOriginalConstructor()->getMock();
42+
43+
/** @var PackageFactory|\PHPUnit_Framework_MockObject_MockObject $packageFactory */
44+
$packageFactory = $this->getMockBuilder(PackageFactory::class)
45+
->disableOriginalConstructor()
46+
->setMethods(['create'])
47+
->getMock();
48+
$packageFactory->method('create')
49+
->willReturnCallback(function ($args) {
50+
return new Package($args['json']);
51+
});
52+
53+
/** @var ComponentRegistrarInterface|\PHPUnit_Framework_MockObject_MockObject $componentRegistrar */
54+
$componentRegistrar = $this->getMockBuilder(ComponentRegistrarInterface::class)
55+
->getMockForAbstractClass();
56+
$componentRegistrar->method('getPaths')
57+
->with(ComponentRegistrar::MODULE)
58+
->willReturn(
59+
$moduleDirectories
60+
);
61+
62+
$directoryReadFactory = $this->getMockBuilder(Filesystem\Directory\ReadInterfaceFactory::class)
63+
->disableOriginalConstructor()
64+
->setMethods(['create'])
65+
->getMock();
66+
$directoryReadFactory->method('create')
67+
->willReturnMap($composerJsonGenerator($this));
68+
69+
$dependency = new Dependency(
70+
$composerInformation,
71+
$filesystem,
72+
$packageFactory,
73+
$componentRegistrar,
74+
$directoryReadFactory
75+
);
76+
$this->assertEquals($expectedPackages, $dependency->getSampleDataPackages());
77+
}
78+
public static function dataPackagesFromComposerSuggest()
79+
{
80+
return [
81+
[
82+
'moduleDirectories' => [
83+
'app/code/LocalModule',
84+
'app/code/LocalModuleWithoutComposerJson',
85+
'vendor/company/module',
86+
'vendor/company2/module/src'
87+
],
88+
'composerJsonGenerator' => function (DependencyTest $test) {
89+
return [
90+
[
91+
['path' => 'app/code/LocalModule'],
92+
$test->stubComposerJsonReader(
93+
[
94+
'name' => 'local/module',
95+
'suggest' => [
96+
'local/module-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '0.1.0'
97+
]
98+
]
99+
)
100+
],
101+
[
102+
['path' => 'app/code/LocalModuleWithoutComposerJson'],
103+
$test->stubFileNotFoundReader()
104+
],
105+
[
106+
['path' => 'vendor/company/module'],
107+
$test->stubComposerJsonReader(
108+
[
109+
'name' => 'company/module',
110+
'suggest' => [
111+
'company/module-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '1.0.0-beta'
112+
]
113+
]
114+
)
115+
],
116+
[
117+
['path' => 'vendor/company2/module/src/..'],
118+
$test->stubComposerJsonReader(
119+
[
120+
'name' => 'company2/module',
121+
'suggest' => [
122+
'company2/module-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '1.10'
123+
]
124+
]
125+
)
126+
],
127+
[
128+
['path' => 'vendor/company2/module/src'],
129+
$test->stubFileNotFoundReader()
130+
],
131+
[
132+
['path' => 'vendor/company/module/..'],
133+
$test->stubFileNotFoundReader()
134+
],
135+
[
136+
['path' => 'app/code/LocalModuleWithoutComposerJson/..'],
137+
$test->stubFileNotFoundReader()
138+
],
139+
[
140+
['path' => 'app/code/LocalModule/..'],
141+
$test->stubFileNotFoundReader()
142+
],
143+
];
144+
},
145+
'suggestions' => [
146+
'magento/foo-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '100.0.0',
147+
'thirdparty/bar-sample-data' => Dependency::SAMPLE_DATA_SUGGEST . '1.2.3',
148+
'thirdparty/something-else' => 'Just a suggested package',
149+
],
150+
'expectedPackages' => [
151+
'magento/foo-sample-data' => '100.0.0',
152+
'thirdparty/bar-sample-data' => '1.2.3',
153+
'local/module-sample-data' => '0.1.0',
154+
'company/module-sample-data' => '1.0.0-beta',
155+
'company2/module-sample-data' => '1.10',
156+
]
157+
]
158+
];
159+
}
160+
161+
public function stubComposerJsonReader(array $composerJsonContent)
162+
{
163+
$stub = $this->getMockBuilder(Filesystem\Directory\ReadInterface::class)
164+
->getMockForAbstractClass();
165+
$stub->method('isExist')
166+
->with('composer.json')
167+
->willReturn(true);
168+
$stub->method('isReadable')
169+
->with('composer.json')
170+
->willReturn(true);
171+
$stub->method('readFile')
172+
->with('composer.json')
173+
->willReturn(json_encode($composerJsonContent));
174+
return $stub;
175+
}
176+
177+
public function stubFileNotFoundReader()
178+
{
179+
$stub = $this->getMockBuilder(Filesystem\Directory\ReadInterface::class)
180+
->getMockForAbstractClass();
181+
$stub->method('isExist')
182+
->with('composer.json')
183+
->willReturn(false);
184+
$stub->method('readFile')
185+
->with('composer.json')
186+
->willThrowException(new FileSystemException(new Phrase('File not found')));
187+
return $stub;
188+
}
189+
}

app/code/Magento/SampleData/etc/di.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@
1515
</argument>
1616
</arguments>
1717
</type>
18+
<virtualType name="Magento\SampleData\Filesystem\Directory\Read" type="Magento\Framework\Filesystem\Directory\Read">
19+
<arguments>
20+
<argument name="driver" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
21+
</arguments>
22+
</virtualType>
23+
<preference for="Magento\Framework\Filesystem\Directory\ReadInterface" type="Magento\SampleData\Filesystem\Directory\Read" />
1824
</config>

0 commit comments

Comments
 (0)