Skip to content

Commit 3d1cd87

Browse files
committed
Add a dedicated file deletion for unconfigure recipes
1 parent 08882d1 commit 3d1cd87

17 files changed

+141
-76
lines changed

src/Configurator.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ class Configurator
2222
{
2323
private $composer;
2424
private $io;
25+
private $filesManager;
2526
private $options;
2627
private $configurators;
2728
private $cache;
2829

29-
public function __construct(Composer $composer, IOInterface $io, Options $options)
30+
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options)
3031
{
3132
$this->composer = $composer;
3233
$this->io = $io;
34+
$this->filesManager = $filesManager;
3335
$this->options = $options;
3436
// ordered list of configurators
3537
$this->configurators = [
@@ -78,6 +80,6 @@ private function get($key): AbstractConfigurator
7880

7981
$class = $this->configurators[$key];
8082

81-
return $this->cache[$key] = new $class($this->composer, $this->io, $this->options);
83+
return $this->cache[$key] = new $class($this->composer, $this->io, $this->filesManager, $this->options);
8284
}
8385
}

src/Configurator/AbstractConfigurator.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Composer\Composer;
1515
use Composer\IO\IOInterface;
16+
use Symfony\Flex\FilesManager;
1617
use Symfony\Flex\Lock;
1718
use Symfony\Flex\Options;
1819
use Symfony\Flex\Path;
@@ -25,13 +26,15 @@ abstract class AbstractConfigurator
2526
{
2627
protected $composer;
2728
protected $io;
29+
protected $filesManager;
2830
protected $options;
2931
protected $path;
3032

31-
public function __construct(Composer $composer, IOInterface $io, Options $options)
33+
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options)
3234
{
3335
$this->composer = $composer;
3436
$this->io = $io;
37+
$this->filesManager = $filesManager;
3538
$this->options = $options;
3639
$this->path = new Path($options->get('root-dir'));
3740
}

src/Configurator/CopyFromPackageConfigurator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private function copyDir(string $source, string $target, array $options)
9494
public function copyFile(string $source, string $target, array $options)
9595
{
9696
$overwrite = $options['force'] ?? false;
97-
if (!$this->options->shouldWriteFile($target, $overwrite)) {
97+
if (!$this->filesManager->shouldWriteFile($target, $overwrite)) {
9898
return;
9999
}
100100

src/Configurator/CopyFromRecipeConfigurator.php

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,7 @@ public function configure(Recipe $recipe, $config, Lock $lock, array $options =
3030
public function unconfigure(Recipe $recipe, $config, Lock $lock)
3131
{
3232
$this->write('Removing files from recipe');
33-
$this->removeFiles($config, $this->getRemovableFilesFromRecipeAndLock($recipe, $lock), $this->options->get('root-dir'));
34-
}
35-
36-
private function getRemovableFilesFromRecipeAndLock(Recipe $recipe, Lock $lock): array
37-
{
38-
$lockedFiles = array_unique(
39-
array_reduce(
40-
array_column($lock->all(), 'files'),
41-
function (array $carry, array $package) {
42-
return array_merge($carry, $package);
43-
},
44-
[]
45-
)
46-
);
47-
48-
$removableFiles = $recipe->getFiles();
49-
50-
$lockedFiles = array_map('realpath', $lockedFiles);
51-
52-
// Compare file paths by their real path to abstract OS differences
53-
foreach (array_keys($removableFiles) as $file) {
54-
if (\in_array(realpath($file), $lockedFiles)) {
55-
unset($removableFiles[$file]);
56-
}
57-
}
58-
59-
return $removableFiles;
33+
$this->removeFiles($config, $this->filesManager->getRemovableFilesFromRecipeAndLock($recipe), $this->options->get('root-dir'));
6034
}
6135

6236
private function copyFiles(array $manifest, array $files, array $options): array
@@ -98,7 +72,7 @@ private function copyFile(string $to, string $contents, bool $executable, array
9872
$basePath = $options['root-dir'] ?? '.';
9973
$copiedFile = str_replace($basePath.\DIRECTORY_SEPARATOR, '', $to);
10074

101-
if (!$this->options->shouldWriteFile($to, $overwrite)) {
75+
if (!$this->filesManager->shouldWriteFile($to, $overwrite)) {
10276
return $copiedFile;
10377
}
10478

src/FilesManager.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Flex;
13+
14+
use Composer\IO\IOInterface;
15+
use Composer\Util\ProcessExecutor;
16+
17+
/**
18+
* @author Maxime Hélias <maximehelias16@gmail.com>
19+
*/
20+
class FilesManager
21+
{
22+
private $io;
23+
protected $path;
24+
25+
private $writtenFiles = [];
26+
private $files;
27+
28+
public function __construct(IOInterface $io, Lock $lock, string $rootDir)
29+
{
30+
$this->io = $io;
31+
32+
$this->path = new Path($rootDir);
33+
$this->files = array_count_values(
34+
array_map(
35+
function (string $file) {
36+
return realpath($file) ?: '';
37+
}, array_reduce(
38+
array_column($lock->all(), 'files'),
39+
function (array $carry, array $package) {
40+
return array_merge($carry, $package);
41+
},
42+
[]
43+
)
44+
)
45+
);
46+
}
47+
48+
public function shouldWriteFile(string $file, bool $overwrite): bool
49+
{
50+
if (isset($this->writtenFiles[$file])) {
51+
return false;
52+
}
53+
$this->writtenFiles[$file] = true;
54+
55+
if (!file_exists($file)) {
56+
return true;
57+
}
58+
59+
if (!$overwrite) {
60+
return false;
61+
}
62+
63+
if (!filesize($file)) {
64+
return true;
65+
}
66+
67+
exec('git status --short --ignored -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status);
68+
69+
if (0 !== $status) {
70+
return (bool) $this->io && $this->io->askConfirmation(sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false);
71+
}
72+
73+
if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) {
74+
return true;
75+
}
76+
77+
$name = basename($file);
78+
$name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name;
79+
80+
return (bool) $this->io && $this->io->askConfirmation(sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false);
81+
}
82+
83+
public function getRemovableFilesFromRecipeAndLock(Recipe $recipe): array
84+
{
85+
$removableFiles = $recipe->getFiles();
86+
// Compare file paths by their real path to abstract OS differences
87+
foreach (array_keys($removableFiles) as $file) {
88+
$file = realpath($file);
89+
if (!isset($this->files[$file])) {
90+
continue;
91+
}
92+
93+
--$this->files[$file];
94+
95+
if ($this->files[$file] <= 0) {
96+
unset($removableFiles[$file]);
97+
}
98+
}
99+
100+
return $removableFiles;
101+
}
102+
}

src/Flex.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
7070

7171
private $config;
7272
private $options;
73+
private $filesManager;
7374
private $configurator;
7475
private $downloader;
7576
private $installer;
@@ -159,8 +160,9 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
159160
$composer->setRepositoryManager($manager);
160161
}
161162

162-
$this->configurator = new Configurator($composer, $io, $this->options);
163163
$this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: str_replace('composer.json', 'symfony.lock', Factory::getComposerFile()));
164+
$this->filesManager = new FilesManager($io, $this->lock, $this->options->get('root-dir'));
165+
$this->configurator = new Configurator($composer, $io, $this->filesManager, $this->options);
164166

165167
$disable = true;
166168
foreach (array_merge($composer->getPackage()->getRequires() ?? [], $composer->getPackage()->getDevRequires() ?? []) as $link) {
@@ -877,7 +879,7 @@ private function initOptions(): Options
877879
'root-dir' => $extra['symfony']['root-dir'] ?? '.',
878880
], $extra);
879881

880-
return new Options($options, $this->io);
882+
return new Options($options);
881883
}
882884

883885
private function getFlexId()

src/Options.php

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,10 @@
2020
class Options
2121
{
2222
private $options;
23-
private $writtenFiles = [];
24-
private $io;
2523

26-
public function __construct(array $options = [], IOInterface $io = null)
24+
public function __construct(array $options = [])
2725
{
2826
$this->options = $options;
29-
$this->io = $io;
3027
}
3128

3229
public function get(string $name)
@@ -46,41 +43,6 @@ public function expandTargetDir(string $target): string
4643
}, $target);
4744
}
4845

49-
public function shouldWriteFile(string $file, bool $overwrite): bool
50-
{
51-
if (isset($this->writtenFiles[$file])) {
52-
return false;
53-
}
54-
$this->writtenFiles[$file] = true;
55-
56-
if (!file_exists($file)) {
57-
return true;
58-
}
59-
60-
if (!$overwrite) {
61-
return false;
62-
}
63-
64-
if (!filesize($file)) {
65-
return true;
66-
}
67-
68-
exec('git status --short --ignored --untracked-files=all -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status);
69-
70-
if (0 !== $status) {
71-
return (bool) $this->io && $this->io->askConfirmation(sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false);
72-
}
73-
74-
if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) {
75-
return true;
76-
}
77-
78-
$name = basename($file);
79-
$name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name;
80-
81-
return (bool) $this->io && $this->io->askConfirmation(sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false);
82-
}
83-
8446
public function toArray(): array
8547
{
8648
return $this->options;

tests/Configurator/BundlesConfiguratorTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public function testConfigure()
2626
$configurator = new BundlesConfigurator(
2727
$this->getMockBuilder('Composer\Composer')->getMock(),
2828
$this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
29+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
2930
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
3031
);
3132

@@ -64,6 +65,7 @@ public function testConfigureWhenBundlesAlreayExists()
6465
$configurator = new BundlesConfigurator(
6566
$this->getMockBuilder('Composer\Composer')->getMock(),
6667
$this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
68+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
6769
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
6870
);
6971

tests/Configurator/ContainerConfiguratorTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public function testConfigure()
3939
$configurator = new ContainerConfigurator(
4040
$this->getMockBuilder(Composer::class)->getMock(),
4141
$this->getMockBuilder(IOInterface::class)->getMock(),
42+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
4243
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
4344
);
4445
$configurator->configure($recipe, ['locale' => 'en'], $lock);
@@ -78,6 +79,7 @@ public function testConfigureWithoutParametersKey()
7879
$configurator = new ContainerConfigurator(
7980
$this->getMockBuilder(Composer::class)->getMock(),
8081
$this->getMockBuilder(IOInterface::class)->getMock(),
82+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
8183
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
8284
);
8385
$configurator->configure($recipe, ['locale' => 'en'], $lock);
@@ -118,6 +120,7 @@ public function testConfigureWithoutDuplicated()
118120
$configurator = new ContainerConfigurator(
119121
$this->getMockBuilder(Composer::class)->getMock(),
120122
$this->getMockBuilder(IOInterface::class)->getMock(),
123+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
121124
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
122125
);
123126
$configurator->configure($recipe, ['locale' => 'en'], $lock);
@@ -162,6 +165,7 @@ public function testConfigureWithComplexContent()
162165
$configurator = new ContainerConfigurator(
163166
$this->getMockBuilder(Composer::class)->getMock(),
164167
$this->getMockBuilder(IOInterface::class)->getMock(),
168+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
165169
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
166170
);
167171
$configurator->configure($recipe, ['locale' => 'en', 'foobar' => 'baz'], $lock);
@@ -212,6 +216,7 @@ public function testConfigureWithComplexContent2()
212216
$configurator = new ContainerConfigurator(
213217
$this->getMockBuilder(Composer::class)->getMock(),
214218
$this->getMockBuilder(IOInterface::class)->getMock(),
219+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
215220
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
216221
);
217222
$configurator->configure($recipe, ['locale' => 'en', 'foobar' => 'baz', 'array' => ['key1' => 'value', 'key2' => "Escape ' one quote"], 'key1' => 'Keep It'], $lock);
@@ -262,6 +267,7 @@ public function testConfigureWithEnvVariable()
262267
$configurator = new ContainerConfigurator(
263268
$this->getMockBuilder(Composer::class)->getMock(),
264269
$this->getMockBuilder(IOInterface::class)->getMock(),
270+
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
265271
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
266272
);
267273
$configurator->configure($recipe, ['env(APP_ENV)' => ''], $lock);

tests/Configurator/CopyDirectoryFromPackageConfiguratorTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Composer\Package\PackageInterface;
1818
use PHPUnit\Framework\TestCase;
1919
use Symfony\Flex\Configurator\CopyFromPackageConfigurator;
20+
use Symfony\Flex\FilesManager;
2021
use Symfony\Flex\Lock;
2122
use Symfony\Flex\Options;
2223
use Symfony\Flex\Recipe;
@@ -106,7 +107,7 @@ protected function tearDown(): void
106107

107108
private function createConfigurator(): CopyFromPackageConfigurator
108109
{
109-
return new CopyFromPackageConfigurator($this->composer, $this->io, new Options(['root-dir' => FLEX_TEST_DIR]));
110+
return new CopyFromPackageConfigurator($this->composer, $this->io, new FilesManager($this->io, $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock(), FLEX_TEST_DIR), new Options(['root-dir' => FLEX_TEST_DIR]));
110111
}
111112

112113
private function cleanUpTargetFiles()

tests/Configurator/CopyFromPackageConfiguratorTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use LogicException;
1919
use PHPUnit\Framework\TestCase;
2020
use Symfony\Flex\Configurator\CopyFromPackageConfigurator;
21+
use Symfony\Flex\FilesManager;
2122
use Symfony\Flex\Lock;
2223
use Symfony\Flex\Options;
2324
use Symfony\Flex\Recipe;
@@ -166,7 +167,7 @@ protected function tearDown(): void
166167

167168
private function createConfigurator(): CopyFromPackageConfigurator
168169
{
169-
return new CopyFromPackageConfigurator($this->composer, $this->io, new Options(['root-dir' => FLEX_TEST_DIR], $this->io));
170+
return new CopyFromPackageConfigurator($this->composer, $this->io, new FilesManager($this->io, $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock(), FLEX_TEST_DIR), new Options(['root-dir' => FLEX_TEST_DIR], $this->io));
170171
}
171172

172173
private function cleanUpTargetFiles()

0 commit comments

Comments
 (0)