Repository: symfony/flex (php)

Issue:
File name too long
Just updated to Symfony Flex 1.17.2 and trying to run `composer recipes` I keep getting:

```bash
$ composer recipes
                                                                                                                                                                                                                                                           
  [ErrorException]                                                                                                                                                                                                                                         
  file_put_contents(/home/cameron/.cache/composer/repo/flex/https---api.github.com-repos-symfony-recipes-contrib-contents-friendsofsymfony.elastica-bundle.5.0.json-ref-flex-main.619ac205ee82c4.90304062.tmp): failed to open stream: File name too long 
```

Xubuntu 21.04, ext4, composer 2.1.12

The cache-key is generated from the URL, which needs to be shortened.

```bash
$ composer recipes -vv

                                                                                                                                                                                                                                                           
  [ErrorException]                                                                                                                                                                                                                                         
  file_put_contents(/home/cameron/.cache/composer/repo/flex/https---api.github.com-repos-symfony-recipes-contrib-contents-friendsofsymfony.elastica-bundle.5.0.json-ref-flex-main.619ac3b00e5ba8.11438895.tmp): failed to open stream: File name too long  
                                                                                                                                                                                                                                                           

Exception trace:
 () at phar:///home/cameron/bin/composer/src/Composer/Cache.php:152
 Composer\Util\ErrorHandler::handle() at n/a:n/a
 file_put_contents() at phar:///home/cameron/bin/composer/src/Composer/Cache.php:152
 Composer\Cache->write() at /home/cameron/Projects/weirdly/vendor/symfony/flex/src/Downloader.php:364
 Symfony\Flex\Downloader->parseJson() at /home/cameron/Projects/weirdly/vendor/symfony/flex/src/Downloader.php:304
 Symfony\Flex\Downloader->Symfony\Flex\{closure}() at n/a:n/a
 call_user_func() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/FulfilledPromise.php:20
 React\Promise\FulfilledPromise->then() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/Deferred.php:74
 React\Promise\Deferred->React\Promise\{closure}() at n/a:n/a
 call_user_func() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/Deferred.php:166
 React\Promise\Deferred->processQueue() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/Deferred.php:95
 React\Promise\Deferred->resolve() at n/a:n/a
 call_user_func() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/FulfilledPromise.php:20
 React\Promise\FulfilledPromise->then() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/Deferred.php:76
 React\Promise\Deferred->React\Promise\{closure}() at n/a:n/a
 call_user_func() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/Deferred.php:166
 React\Promise\Deferred->processQueue() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/Deferred.php:95
 React\Promise\Deferred->resolve() at phar:///home/cameron/bin/composer/vendor/react/promise/src/React/Promise/Promise.php:42
 React\Promise\Promise->React\Promise\{closure}() at n/a:n/a
 call_user_func() at phar:///home/cameron/bin/composer/src/Composer/Util/Http/CurlDownloader.php:415
 Composer\Util\Http\CurlDownloader->tick() at phar:///home/cameron/bin/composer/src/Composer/Util/HttpDownloader.php:403
 Composer\Util\HttpDownloader->countActiveJobs() at phar:///home/cameron/bin/composer/src/Composer/Util/Loop.php:96
 Composer\Util\Loop->wait() at /home/cameron/Projects/weirdly/vendor/symfony/flex/src/Downloader.php:310
 Symfony\Flex\Downloader->get() at /home/cameron/Projects/weirdly/vendor/symfony/flex/src/Downloader.php:229
 Symfony\Flex\Downloader->getRecipes() at /home/cameron/Projects/weirdly/vendor/symfony/flex/src/Flex.php:703
 Symfony\Flex\Flex->fetchRecipes() at /home/cameron/Projects/weirdly/vendor/symfony/flex/src/Command/RecipesCommand.php:84
 Symfony\Flex\Command\RecipesCommand->execute() at phar:///home/cameron/bin/composer/vendor/symfony/console/Command/Command.php:245
 Symfony\Component\Console\Command\Command->run() at phar:///home/cameron/bin/composer/vendor/symfony/console/Application.php:835
 Symfony\Component\Console\Application->doRunCommand() at phar:///home/cameron/bin/composer/vendor/symfony/console/Application.php:185
 Symfony\Component\Console\Application->doRun() at phar:///home/cameron/bin/composer/src/Composer/Console/Application.php:327
 Composer\Console\Application->doRun() at phar:///home/cameron/bin/composer/vendor/symfony/console/Application.php:117
 Symfony\Component\Console\Application->run() at phar:///home/cameron/bin/composer/src/Composer/Console/Application.php:128
 Composer\Console\Application->run() at phar:///home/cameron/bin/composer/bin/composer:73
 require() at /home/cameron/bin/composer:29
```

Pull request description:
Shorten Symfony Recipe URL Cache Keys
Fixes #830.

This change replaces the simple generated composer filesystem cache key with a slightly shorter one only for the Symfony Recipes repos.

`/home/cameron/.cache/composer/repo/flex/https---api.github.com-repos-symfony-recipes-contrib-contents-friendsofsymfony.elastica-bundle.5.0.json-ref-flex-main` 
becomes
`/home/cameron/.cache/composer/repo/flex/sf-recipes-contrib-friendsofsymfony.elastica-bundle.5.0.json-ref-flex-main`

This won't fix other recipe files hosted on long paths.

Patch:
diff --git a/composer.json b/composer.json
index 9fbe22c..d0e6d1f 100644
--- a/composer.json
+++ b/composer.json
@@ -16,10 +16,10 @@
     },
     "require-dev": {
         "composer/composer": "^1.0.2|^2.0",
-        "symfony/dotenv": "^4.4|^5.0|^6.0",
-        "symfony/filesystem": "^4.4|^5.0|^6.0",
-        "symfony/phpunit-bridge": "^4.4.12|^5.0|^6.0",
-        "symfony/process": "^4.4|^5.0|^6.0"
+        "symfony/dotenv": "^4.4|^5.0",
+        "symfony/filesystem": "^4.4|^5.0",
+        "symfony/phpunit-bridge": "^4.4.12|^5.0",
+        "symfony/process": "^3.4|^4.4|^5.0"
     },
     "autoload": {
         "psr-4": {
@@ -27,6 +27,9 @@
         }
     },
     "extra": {
+        "branch-alias": {
+            "dev-main": "1.17-dev"
+        },
         "class": "Symfony\\Flex\\Flex"
     }
 }
diff --git a/src/Configurator/DockerComposeConfigurator.php b/src/Configurator/DockerComposeConfigurator.php
index 103c73d..819180e 100644
--- a/src/Configurator/DockerComposeConfigurator.php
+++ b/src/Configurator/DockerComposeConfigurator.php
@@ -12,10 +12,7 @@
 namespace Symfony\Flex\Configurator;
 
 use Composer\Composer;
-use Composer\Factory;
 use Composer\IO\IOInterface;
-use Composer\Json\JsonFile;
-use Composer\Json\JsonManipulator;
 use Symfony\Component\Filesystem\Filesystem;
 use Symfony\Flex\Lock;
 use Symfony\Flex\Options;
@@ -30,8 +27,6 @@ class DockerComposeConfigurator extends AbstractConfigurator
 {
     private $filesystem;
 
-    public static $configureDockerRecipes = null;
-
     public function __construct(Composer $composer, IOInterface $io, Options $options)
     {
         parent::__construct($composer, $io, $options);
@@ -41,7 +36,8 @@ class DockerComposeConfigurator extends AbstractConfigurator
 
     public function configure(Recipe $recipe, $config, Lock $lock, array $options = [])
     {
-        if (!self::shouldConfigureDockerRecipe($this->composer, $this->io, $recipe)) {
+        $installDocker = $this->composer->getPackage()->getExtra()['symfony']['docker'] ?? true;
+        if (!$installDocker) {
             return;
         }
 
@@ -132,72 +128,6 @@ class DockerComposeConfigurator extends AbstractConfigurator
         $this->write('Docker Compose definitions have been modified. Please run "docker-compose up" again to apply the changes.');
     }
 
-    public static function shouldConfigureDockerRecipe(Composer $composer, IOInterface $io, Recipe $recipe): bool
-    {
-        if (null !== self::$configureDockerRecipes) {
-            return self::$configureDockerRecipes;
-        }
-
-        if (null !== $dockerPreference = $composer->getPackage()->getExtra()['symfony']['docker'] ?? null) {
-            self::$configureDockerRecipes = $dockerPreference;
-
-            return self::$configureDockerRecipes;
-        }
-
-        if ('install' !== $recipe->getJob()) {
-            // default to not configuring
-            return false;
-        }
-
-        $warning = $io->isInteractive() ? 'WARNING' : 'IGNORING';
-        $io->writeError(sprintf('  - <warning> %s </> %s', $warning, $recipe->getFormattedOrigin()));
-        $question = '    The recipe for this package contains some Docker configuration.
-
-    This may create/update <comment>docker-compose.yml</comment> or update <comment>Dockerfile</comment> (if it exists).
-
-    Do you want to include Docker configuration from recipes?
-    [<comment>y</>] Yes
-    [<comment>n</>] No
-    [<comment>p</>] Yes permanently, never ask again for this project
-    [<comment>x</>] No permanently, never ask again for this project
-    (defaults to <comment>y</>): ';
-        $answer = $io->askAndValidate(
-            $question,
-            function ($value) {
-                if (null === $value) {
-                    return 'y';
-                }
-                $value = strtolower($value[0]);
-                if (!\in_array($value, ['y', 'n', 'p', 'x'], true)) {
-                    throw new \InvalidArgumentException('Invalid choice.');
-                }
-
-                return $value;
-            },
-            null,
-            'y'
-        );
-        if ('n' === $answer) {
-            self::$configureDockerRecipes = false;
-
-            return self::$configureDockerRecipes;
-        }
-        if ('y' === $answer) {
-            self::$configureDockerRecipes = true;
-
-            return self::$configureDockerRecipes;
-        }
-
-        // yes or no permanently
-        self::$configureDockerRecipes = 'p' === $answer;
-        $json = new JsonFile(Factory::getComposerFile());
-        $manipulator = new JsonManipulator(file_get_contents($json->getPath()));
-        $manipulator->addSubNode('extra', 'symfony.docker', self::$configureDockerRecipes);
-        file_put_contents($json->getPath(), $manipulator->getContents());
-
-        return self::$configureDockerRecipes;
-    }
-
     /**
      * Normalizes the config and return the name of the main Docker Compose file if applicable.
      */
diff --git a/src/Configurator/DockerfileConfigurator.php b/src/Configurator/DockerfileConfigurator.php
index ba8bbbb..e7063a9 100644
--- a/src/Configurator/DockerfileConfigurator.php
+++ b/src/Configurator/DockerfileConfigurator.php
@@ -23,7 +23,8 @@ class DockerfileConfigurator extends AbstractConfigurator
 {
     public function configure(Recipe $recipe, $config, Lock $lock, array $options = [])
     {
-        if (!DockerComposeConfigurator::shouldConfigureDockerRecipe($this->composer, $this->io, $recipe)) {
+        $installDocker = $this->composer->getPackage()->getExtra()['symfony']['docker'] ?? true;
+        if (!$installDocker) {
             return;
         }
 
diff --git a/src/Downloader.php b/src/Downloader.php
index 341a082..53557cb 100644
--- a/src/Downloader.php
+++ b/src/Downloader.php
@@ -271,7 +271,7 @@ class Downloader
         $options = [];
 
         foreach ($urls as $url) {
-            $cacheKey = preg_replace('{[^a-z0-9.]}i', '-', $url);
+            $cacheKey = self::generateCacheKey($url);
             $headers = [];
 
             if (preg_match('{^https?://api\.github\.com/}', $url)) {
@@ -300,7 +300,7 @@ class Downloader
             foreach ($urls as $url) {
                 $jobs[] = $this->rfs->add($url, $options[$url])->then(function (ComposerResponse $response) use ($url, &$responses) {
                     if (200 === $response->getStatusCode()) {
-                        $cacheKey = preg_replace('{[^a-z0-9.]}i', '-', $url);
+                        $cacheKey = self::generateCacheKey($url);
                         $responses[$url] = $this->parseJson($response->getBody(), $url, $cacheKey, $response->getHeaders())->getBody();
                     }
                 }, function (\Exception $e) use ($url, &$retries) {
@@ -314,7 +314,7 @@ class Downloader
             }
             $this->rfs->download($urls, function ($url) use ($options, &$responses, &$retries, &$error) {
                 try {
-                    $cacheKey = preg_replace('{[^a-z0-9.]}i', '-', $url);
+                    $cacheKey = self::generateCacheKey($url);
                     $origin = method_exists($this->rfs, 'getOrigin') ? $this->rfs::getOrigin($url) : parse_url($url, \PHP_URL_HOST);
                     $json = $this->rfs->getContents($origin, $url, false, $options[$url]);
                     if (200 === $this->rfs->findStatusCode($this->rfs->getLastHeaders())) {
@@ -412,4 +412,11 @@ class Downloader
             $this->endpoints[$endpoint] = $config;
         }
     }
+
+    private static function generateCacheKey(string $url): string
+    {
+        $url = preg_replace('{^https://api.github.com/repos/([^/]++/[^/]++)/contents/}', '$1/', $url);
+
+        return preg_replace('{[^a-z0-9.]}i', '-', $url);
+    }
 }
diff --git a/src/Flex.php b/src/Flex.php
index fbe35b1..c97e8f2 100644
--- a/src/Flex.php
+++ b/src/Flex.php
@@ -97,6 +97,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
         'remove' => false,
         'unpack' => true,
     ];
+    private $shouldUpdateComposerLock = false;
     private $filter;
 
     public function activate(Composer $composer, IOInterface $io)
@@ -426,11 +427,10 @@ class Flex implements PluginInterface, EventSubscriberInterface
         $this->io->writeError(sprintf('<info>Symfony operations: %d recipe%s (%s)</>', \count($recipes), \count($recipes) > 1 ? 's' : '', $this->downloader->getSessionId()));
         $installContribs = $this->composer->getPackage()->getExtra()['symfony']['allow-contrib'] ?? false;
         $manifest = null;
-        $originalComposerJsonHash = $this->getComposerJsonHash();
         foreach ($recipes as $recipe) {
             if ('install' === $recipe->getJob() && !$installContribs && $recipe->isContrib()) {
                 $warning = $this->io->isInteractive() ? 'WARNING' : 'IGNORING';
-                $this->io->writeError(sprintf('  - <warning> %s </> %s', $warning, $recipe->getFormattedOrigin()));
+                $this->io->writeError(sprintf('  - <warning> %s </> %s', $warning, $this->formatOrigin($recipe->getOrigin())));
                 $question = sprintf('    The recipe for this package comes from the "contrib" repository, which is open to community contributions.
     Review the recipe at %s
 
@@ -468,12 +468,13 @@ class Flex implements PluginInterface, EventSubscriberInterface
                     $manipulator = new JsonManipulator(file_get_contents($json->getPath()));
                     $manipulator->addSubNode('extra', 'symfony.allow-contrib', true);
                     file_put_contents($json->getPath(), $manipulator->getContents());
+                    $this->shouldUpdateComposerLock = true;
                 }
             }
 
             switch ($recipe->getJob()) {
                 case 'install':
-                    $this->io->writeError(sprintf('  - Configuring %s', $recipe->getFormattedOrigin()));
+                    $this->io->writeError(sprintf('  - Configuring %s', $this->formatOrigin($recipe->getOrigin())));
                     $this->configurator->install($recipe, $this->lock, [
                         'force' => $event instanceof UpdateEvent && $event->force(),
                     ]);
@@ -490,7 +491,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
                 case 'update':
                     break;
                 case 'uninstall':
-                    $this->io->writeError(sprintf('  - Unconfiguring %s', $recipe->getFormattedOrigin()));
+                    $this->io->writeError(sprintf('  - Unconfiguring %s', $this->formatOrigin($recipe->getOrigin())));
                     $this->configurator->unconfigure($recipe, $this->lock);
                     break;
             }
@@ -511,7 +512,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
         $this->synchronizePackageJson($rootDir);
         $this->lock->write();
 
-        if ($this->getComposerJsonHash() !== $originalComposerJsonHash) {
+        if ($this->shouldUpdateComposerLock) {
             $this->updateComposerLock();
         }
     }
@@ -807,6 +808,16 @@ class Flex implements PluginInterface, EventSubscriberInterface
         return new Options($options, $this->io);
     }
 
+    private function formatOrigin(string $origin): string
+    {
+        // symfony/translation:3.3@github.com/symfony/recipes:branch
+        if (!preg_match('/^([^:]++):([^@]++)@(.+)$/', $origin, $matches)) {
+            return $origin;
+        }
+
+        return sprintf('<info>%s</> (<comment>>=%s</>): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? '<comment>'.$matches[3].'</>' : $matches[3]);
+    }
+
     private function shouldRecordOperation(PackageEvent $event): bool
     {
         $operation = $event->getOperation();
@@ -970,9 +981,4 @@ class Flex implements PluginInterface, EventSubscriberInterface
 
         return $events;
     }
-
-    private function getComposerJsonHash(): string
-    {
-        return md5_file(Factory::getComposerFile());
-    }
 }
diff --git a/src/Recipe.php b/src/Recipe.php
index a45a1e5..4c12049 100644
--- a/src/Recipe.php
+++ b/src/Recipe.php
@@ -67,20 +67,6 @@ class Recipe
         return $this->data['origin'] ?? '';
     }
 
-    public function getFormattedOrigin(): string
-    {
-        if (!$this->getOrigin()) {
-            return '';
-        }
-
-        // symfony/translation:3.3@github.com/symfony/recipes:branch
-        if (!preg_match('/^([^:]++):([^@]++)@(.+)$/', $this->getOrigin(), $matches)) {
-            return $this->getOrigin();
-        }
-
-        return sprintf('<info>%s</> (<comment>>=%s</>): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? '<comment>'.$matches[3].'</>' : $matches[3]);
-    }
-
     public function getURL(): string
     {
         if (!$this->data['origin']) {


Test patch:
diff --git a/tests/Configurator/DockerComposeConfiguratorTest.php b/tests/Configurator/DockerComposeConfiguratorTest.php
index a0ea32d..047994b 100644
--- a/tests/Configurator/DockerComposeConfiguratorTest.php
+++ b/tests/Configurator/DockerComposeConfiguratorTest.php
@@ -107,29 +107,13 @@ YAML;
     /** @var Composer|\PHPUnit\Framework\MockObject\MockObject */
     private $composer;
 
-    /** @var IOInterface|\PHPUnit\Framework\MockObject\MockObject */
-    private $io;
-
     /** @var DockerComposeConfigurator */
     private $configurator;
 
-    /** @var Package */
-    private $package;
-
-    private $originalEnvComposer;
-
     protected function setUp(): void
     {
         @mkdir(FLEX_TEST_DIR);
 
-        $this->originalEnvComposer = $_SERVER['COMPOSER'] ?? null;
-        $_SERVER['COMPOSER'] = FLEX_TEST_DIR.'/composer.json';
-        // composer 2.1 and lower support
-        putenv('COMPOSER='.FLEX_TEST_DIR.'/composer.json');
-
-        // reset state
-        DockerComposeConfigurator::$configureDockerRecipes = null;
-
         // Recipe
         $this->recipeDb = $this->getMockBuilder(Recipe::class)->disableOriginalConstructor()->getMock();
         $this->recipeDb->method('getName')->willReturn('doctrine/doctrine-bundle');
@@ -138,17 +122,15 @@ YAML;
         $this->lock = $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock();
 
         // Configurator
-        $this->package = new Package('dummy/dummy', '1.0.0', '1.0.0');
-        $this->package->setExtra(['symfony' => ['docker' => true]]);
+        $package = new Package('dummy/dummy', '1.0.0', '1.0.0');
+        $package->setExtra(['symfony' => ['docker' => true]]);
 
         $this->composer = $this->getMockBuilder(Composer::class)->getMock();
-        $this->composer->method('getPackage')->willReturn($this->package);
-
-        $this->io = $this->getMockBuilder(IOInterface::class)->getMock();
+        $this->composer->method('getPackage')->willReturn($package);
 
         $this->configurator = new DockerComposeConfigurator(
             $this->composer,
-            $this->io,
+            $this->getMockBuilder(IOInterface::class)->getMock(),
             new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
         );
     }
@@ -156,19 +138,11 @@ YAML;
     protected function tearDown(): void
     {
         unset($_SERVER['COMPOSE_FILE']);
-        if ($this->originalEnvComposer) {
-            $_SERVER['COMPOSER'] = $this->originalEnvComposer;
-        } else {
-            unset($_SERVER['COMPOSER']);
-        }
-        // composer 2.1 and lower support
-        putenv('COMPOSER='.$this->originalEnvComposer);
 
         (new Filesystem())->remove([
             FLEX_TEST_DIR.'/docker-compose.yml',
             FLEX_TEST_DIR.'/docker-compose.override.yml',
             FLEX_TEST_DIR.'/docker-compose.yaml',
-            FLEX_TEST_DIR.'/composer.json',
             FLEX_TEST_DIR.'/child/docker-compose.override.yaml',
             FLEX_TEST_DIR.'/child',
         ]);
@@ -210,52 +184,6 @@ YAML
         $this->assertEquals(self::ORIGINAL_CONTENT, file_get_contents($dockerComposeFile));
     }
 
-    public function testNotConfiguredIfConfigSet()
-    {
-        $this->package->setExtra(['symfony' => ['docker' => false]]);
-        $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock);
-
-        $this->assertFileDoesNotExist(FLEX_TEST_DIR.'/docker-compose.yml');
-    }
-
-    /**
-     * @dataProvider getInteractiveDockerPreferenceTests
-     */
-    public function testPreferenceAskedInteractively(string $userInput, bool $expectedIsConfigured, bool $expectedIsComposerJsonUpdated)
-    {
-        $composerJsonPath = FLEX_TEST_DIR.'/composer.json';
-        file_put_contents($composerJsonPath, json_encode(['name' => 'test/app']));
-
-        $this->package->setExtra(['symfony' => []]);
-        $this->recipeDb->method('getJob')->willReturn('install');
-        $this->io->method('isInteractive')->willReturn(true);
-        $this->io->expects($this->once())->method('askAndValidate')->willReturn($userInput);
-
-        $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock);
-
-        if ($expectedIsConfigured) {
-            $this->assertFileExists(FLEX_TEST_DIR.'/docker-compose.yml');
-        } else {
-            $this->assertFileDoesNotExist(FLEX_TEST_DIR.'/docker-compose.yml');
-        }
-
-        $composerJsonData = json_decode(file_get_contents($composerJsonPath), true);
-        if ($expectedIsComposerJsonUpdated) {
-            $this->assertArrayHasKey('extra', $composerJsonData);
-            $this->assertSame($expectedIsConfigured, $composerJsonData['extra']['symfony']['docker']);
-        } else {
-            $this->assertArrayNotHasKey('extra', $composerJsonData);
-        }
-    }
-
-    public function getInteractiveDockerPreferenceTests()
-    {
-        yield 'yes_once' => ['y', true, false];
-        yield 'no_once' => ['n', false, false];
-        yield 'yes_forever' => ['p', true, true];
-        yield 'no_forever' => ['x', false, true];
-    }
-
     public function testConfigureFileWithExistingVolumes()
     {
         $originalContent = self::ORIGINAL_CONTENT.<<<'YAML'
diff --git a/tests/UnpackerTest.php b/tests/UnpackerTest.php
index 4ca7741..f9e9e4e 100644
--- a/tests/UnpackerTest.php
+++ b/tests/UnpackerTest.php
@@ -39,9 +39,7 @@ class UnpackerTest extends TestCase
         @unlink($composerJsonPath);
         file_put_contents($composerJsonPath, '{}');
 
-        $originalEnvComposer = $_SERVER['COMPOSER'];
-        $_SERVER['COMPOSER'] = $composerJsonPath;
-        // composer 2.1 and lower support
+        $originalEnvComposer = getenv('COMPOSER');
         putenv('COMPOSER='.$composerJsonPath);
 
         // Setup packages
@@ -51,11 +49,11 @@ class UnpackerTest extends TestCase
 
         $virtualPkgFoo = new Package('pack_foo', '1.0.0', '1.0.0');
         $virtualPkgFoo->setType('symfony-pack');
-        $virtualPkgFoo->setRequires(['real' => $realPkgLink]);
+        $virtualPkgFoo->setRequires([$realPkgLink]);
 
         $virtualPkgBar = new Package('pack_bar', '1.0.0', '1.0.0');
         $virtualPkgBar->setType('symfony-pack');
-        $virtualPkgBar->setRequires(['real' => $realPkgLink]);
+        $virtualPkgBar->setRequires([$realPkgLink]);
 
         $packages = [$realPkg, $virtualPkgFoo, $virtualPkgBar];
 
@@ -89,12 +87,6 @@ class UnpackerTest extends TestCase
 
         // Restore
 
-        if ($originalEnvComposer) {
-            $_SERVER['COMPOSER'] = $originalEnvComposer;
-        } else {
-            unset($_SERVER['COMPOSER']);
-        }
-        // composer 2.1 and lower support
         putenv('COMPOSER='.$originalEnvComposer);
         @unlink($composerJsonPath);
     }