Skip to content

Commit d52f05a

Browse files
authored
Merge pull request #4 from YouweGit/feature/support-skipping-version-overrides
feat: support skipping package installation when upstream has out-of-…
2 parents 7ba051b + 3473da4 commit d52f05a

File tree

7 files changed

+166
-76
lines changed

7 files changed

+166
-76
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/vendor/
22
composer.lock
3+
.idea/
4+
.phpunit.result.cache

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## 1.5.0
8+
### Changed
9+
- Add support whether upstream projects should have versions replaced in composer.json if the version does not match.
10+
This can be decided per package individually, and the default value is the behavior of 1.4.0 where a package will always be replaced if the versions do not match.
11+
- Enforce this package is unit tested with phpunit 12. Since the dev dependency was on stable, phpunit 12 would be installed anyway on modernized systems.
12+
713
## 1.4.0
814
### Added
915
- Option to update module with all dependencies.

README.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
11
# composer-dependency-installer
22

3-
PHP package for installing Composer's dependencies.
3+
PHP package for installing Composer's dependencies upstream.
4+
5+
## Usage
6+
To install a package in upstream software requiring this package, instantiate a new instance and call the install() function.
7+
8+
## Usage reference
9+
```php
10+
<?php
11+
12+
declare(strict_types=1);
13+
14+
namespace Acme\Foo;
15+
16+
class Bar
17+
{
18+
public function __construct(
19+
private readonly DependencyInstaller $dependencyInstaller
20+
) {}
21+
22+
public function installUpstreamRepository(): void
23+
{
24+
$this->dependencyInstaller->installRepository(
25+
name: 'some/package',
26+
type: 'composer',
27+
url: 'mypackagedomain.dev'
28+
);
29+
}
30+
31+
public function installUpstreamPackage(): void
32+
{
33+
$this->dependencyInstaller->installPackage(
34+
name: 'some/package',
35+
version: '^2.0.0',
36+
dev: true, // Whether it should be a dev dependency (e.g. require-dev). Optional, defaults to true
37+
updateDependencies: false, // Whether dependencies can be updated. Optional, defaults to true. When enabled, passes -W flag to composer
38+
allowOverrideVersion: true // Whether version can be updated with new version when package is altready installed upstream. Optional, defaults to true.
39+
)
40+
}
41+
}
42+
```

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
},
2626
"require-dev": {
2727
"kint-php/kint": "@stable",
28-
"phpunit/phpunit": "@stable"
28+
"phpunit/phpunit": "^12"
2929
},
3030
"autoload": {
3131
"psr-4": {

phpunit.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,4 @@
77
<directory>tests</directory>
88
</testsuite>
99
</testsuites>
10-
<filter>
11-
<whitelist processUncoveredFilesFromWhitelist="true">
12-
<directory suffix=".php">src</directory>
13-
</whitelist>
14-
</filter>
1510
</phpunit>

src/DependencyInstaller.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,24 @@ public function installRepository(string $name, string $type, string $url)
9999
* @param string $version
100100
* @param bool $dev
101101
* @param bool $updateDependencies
102+
* @param bool $allowOverrideVersion
102103
*
103104
* @return void
104105
*
105106
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
106107
*/
107-
public function installPackage(string $name, string $version, bool $dev = true, bool $updateDependencies = false)
108-
{
108+
public function installPackage(
109+
string $name,
110+
string $version,
111+
bool $dev = true,
112+
bool $updateDependencies = false,
113+
bool $allowOverrideVersion = true
114+
) {
109115
$node = $dev ? 'require-dev' : 'require';
110116

111117
if (array_key_exists($node, $this->definition)
112118
&& array_key_exists($name, $this->definition[$node])
113-
&& $this->definition[$node][$name] === $version
119+
&& ($this->definition[$node][$name] === $version || !$allowOverrideVersion)
114120
) {
115121
return;
116122
}

tests/DependencyInstallerTest.php

Lines changed: 108 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,44 @@
1010
namespace Youwe\Composer\DependencyInstaller\Tests;
1111

1212
use Composer\Json\JsonFile;
13+
use PHPUnit\Framework\Attributes\CoversClass;
14+
use PHPUnit\Framework\Attributes\CoversMethod;
15+
use PHPUnit\Framework\Attributes\DataProvider;
16+
use PHPUnit\Framework\MockObject\Exception;
1317
use Seld\JsonLint\ParsingException;
1418
use Youwe\Composer\DependencyInstaller\DependencyInstaller;
1519
use PHPUnit\Framework\TestCase;
1620
use Symfony\Component\Console\Output\OutputInterface;
1721

18-
/**
19-
* @coversDefaultClass \Youwe\Composer\DependencyInstaller\DependencyInstaller
20-
*/
22+
#[CoversClass(DependencyInstaller::class)]
23+
#[CoversMethod(DependencyInstaller::class, '__construct')]
24+
#[CoversMethod(DependencyInstaller::class, 'installPackage')]
25+
#[CoversMethod(DependencyInstaller::class, 'installRepository')]
2126
class DependencyInstallerTest extends TestCase
2227
{
23-
/** @var string */
24-
private static $directory = __DIR__ . DIRECTORY_SEPARATOR . 'tmp';
28+
private static string $directory = __DIR__ . DIRECTORY_SEPARATOR . 'tmp';
29+
private DependencyInstaller $dependencyInstaller;
2530

2631
/**
27-
* @return void
32+
* @throws Exception
2833
*/
29-
public static function setUpBeforeClass()
34+
protected function setUp(): void
3035
{
31-
static::tearDownAfterClass();
32-
3336
mkdir(static::$directory);
3437
chdir(static::$directory);
3538
file_put_contents('composer.json', '{}');
39+
40+
$this->updateComposerFileReference();
3641
}
3742

38-
/**
39-
* @return void
40-
*/
41-
public static function tearDownAfterClass()
43+
protected function tearDown(): void
4244
{
4345
if (is_dir(static::$directory)) {
4446
static::rrmdir(static::$directory);
4547
}
4648
}
4749

48-
/**
49-
* @param string $src
50-
*
51-
* @return void
52-
*/
53-
private static function rrmdir(string $src)
50+
private static function rrmdir(string $src): void
5451
{
5552
$dir = opendir($src);
5653
while (false !== ($file = readdir($dir))) {
@@ -67,45 +64,24 @@ private static function rrmdir(string $src)
6764
rmdir($src);
6865
}
6966

70-
71-
/**
72-
* @return DependencyInstaller
73-
* @covers ::__construct
74-
*/
75-
public function testConstructor(): DependencyInstaller
67+
public function testConstructor(): void
7668
{
77-
$installer = new DependencyInstaller(
78-
'composer.json',
79-
$this->createMock(OutputInterface::class)
80-
);
81-
8269
$this->assertInstanceOf(
8370
DependencyInstaller::class,
84-
$installer
71+
$this->dependencyInstaller
8572
);
86-
87-
return $installer;
8873
}
8974

9075
/**
91-
* @depends testConstructor
92-
* @dataProvider repositoryProvider
93-
*
94-
* @param string $name
95-
* @param string $type
96-
* @param string $url
97-
* @param DependencyInstaller $dependencyInstaller
98-
*
99-
* @return void
100-
* @covers ::installRepository
76+
* @throws ParsingException
10177
*/
78+
#[DataProvider('repositoryProvider')]
10279
public function testInstallRepository(
10380
string $name,
10481
string $type,
10582
string $url,
106-
DependencyInstaller $dependencyInstaller
107-
) {
108-
$dependencyInstaller->installRepository($name, $type, $url);
83+
): void {
84+
$this->dependencyInstaller->installRepository($name, $type, $url);
10985

11086
$jsonFile = new JsonFile('composer.json');
11187
$definition = $jsonFile->read();
@@ -121,35 +97,25 @@ public function testInstallRepository(
12197
/**
12298
* @return array
12399
*/
124-
public function repositoryProvider(): array
100+
public static function repositoryProvider(): array
125101
{
126102
return [
127103
['mediact', 'composer', 'https://composer.mediact.nl']
128104
];
129105
}
130106

131107
/**
132-
* @depends testConstructor
133-
* @dataProvider packageProvider
134-
*
135-
* @param string $name
136-
* @param string $version
137-
* @param bool $dev
138-
* @param bool $updateDependencies
139-
* @param DependencyInstaller $dependencyInstaller
140-
*
141-
* @return void
142108
* @throws ParsingException
143-
* @covers ::installPackage
144109
*/
110+
#[DataProvider('packageProvider')]
145111
public function testInstallPackage(
146112
string $name,
147113
string $version,
148114
bool $dev,
149115
bool $updateDependencies,
150-
DependencyInstaller $dependencyInstaller
116+
bool $allowOverrideVersion
151117
) {
152-
$dependencyInstaller->installPackage($name, $version, $dev, $updateDependencies);
118+
$this->dependencyInstaller->installPackage($name, $version, $dev, $updateDependencies, $allowOverrideVersion);
153119

154120
$jsonFile = new JsonFile('composer.json');
155121
$definition = $jsonFile->read();
@@ -161,14 +127,90 @@ public function testInstallPackage(
161127
$this->assertEquals($version, $definition[$node][$name]);
162128
}
163129

164-
/**
165-
* @return array
166-
*/
167-
public function packageProvider(): array
130+
public static function packageProvider(): array
168131
{
169132
return [
170-
['psr/log', '@stable', true, false],
171-
['psr/log', '@stable', false, false]
133+
['psr/log', '@stable', true, false, true],
134+
['psr/log', '@stable', false, false, true]
172135
];
173136
}
137+
138+
/**
139+
* @throws ParsingException
140+
* @throws Exception
141+
*/
142+
public function testOriginalPackageVersionCanBePreserved() {
143+
$this->dependencyInstaller->installPackage(
144+
'psr/link',
145+
'1.1.1',
146+
false,
147+
false,
148+
false
149+
);
150+
151+
$this->updateComposerFileReference();
152+
153+
$this->dependencyInstaller->installPackage(
154+
'psr/link',
155+
'^2',
156+
false,
157+
false,
158+
false
159+
);
160+
161+
$this->updateComposerFileReference();
162+
163+
$jsonFile = new JsonFile('composer.json');
164+
$definition = $jsonFile->read();
165+
166+
// Assert the pre-existing composer versions was NOT wiped by a dependency installer package installation
167+
$this->assertArrayHasKey('require', $definition);
168+
$this->assertArrayHasKey('psr/link', $definition['require']);
169+
$this->assertEquals('1.1.1', $definition['require']['psr/link']);
170+
}
171+
172+
/**
173+
* @throws ParsingException|Exception
174+
*/
175+
public function testOriginalPackageVersionCanBeOverridden(): void {
176+
// Install and validate base package availability
177+
$this->dependencyInstaller->installPackage(
178+
'psr/link',
179+
'1.1.1',
180+
false
181+
);
182+
183+
$this->updateComposerFileReference();
184+
185+
$this->dependencyInstaller->installPackage(
186+
'psr/link',
187+
'@stable',
188+
false
189+
);
190+
191+
$jsonFile = new JsonFile('composer.json');
192+
$definition = $jsonFile->read();
193+
194+
$this->assertArrayHasKey('require', $definition);
195+
$this->assertArrayHasKey('psr/link', $definition['require']);
196+
$this->assertEquals('@stable', $definition['require']['psr/link']);
197+
}
198+
199+
/**
200+
* Update the composer file reference since it is loaded only once as part of the constructor.
201+
*
202+
* We refresh the composer file reference whenever the composer file is changed, otherwise the dependency
203+
* installer will always hold a reference to the previous state of the composer file and not contain any
204+
* changes if multiple installations happen in the same testing function.
205+
*
206+
* @return void
207+
* @throws Exception
208+
*/
209+
private function updateComposerFileReference(): void
210+
{
211+
$this->dependencyInstaller = new DependencyInstaller(
212+
'composer.json',
213+
$this->createMock(OutputInterface::class)
214+
);
215+
}
174216
}

0 commit comments

Comments
 (0)