From 3b78505f5dda608f3e9e6fa8f27ae38a222ad4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jun 2026 09:29:38 +0200 Subject: [PATCH 1/2] Warn when SYMFONY_REQUIRE is set to an exact version constraint --- src/Flex.php | 4 ++++ tests/FlexTest.php | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/Flex.php b/src/Flex.php index 6c53270a..bdecd2d3 100644 --- a/src/Flex.php +++ b/src/Flex.php @@ -221,6 +221,10 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) $symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? '')); + if ($symfonyRequire && preg_match('/^\d+(\.\d+)*$/', $symfonyRequire)) { + $io->writeError(\sprintf('SYMFONY_REQUIRE="%s" is an exact version constraint. Did you mean "%s.*" or "^%s"?', $symfonyRequire, $symfonyRequire, $symfonyRequire)); + } + if ($symfonyRequire || $this->ignorePreleases) { $this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader, $this->ignorePreleases); } diff --git a/tests/FlexTest.php b/tests/FlexTest.php index ef82da5b..2e7b948d 100644 --- a/tests/FlexTest.php +++ b/tests/FlexTest.php @@ -111,6 +111,47 @@ public function testPostInstall() ); } + #[DataProvider('getSymfonyRequireConstraints')] + public function testSymfonyRequireExactVersionWarning(string $constraint, bool $expectWarning): void + { + $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE); + + $package = $this->mockRootPackage(); + $package->method('getRequires')->willReturn([new Link('dummy', 'symfony/flex', class_exists(MatchAllConstraint::class) ? new MatchAllConstraint() : null)]); + + $composer = $this->mockComposer($this->mockLocker(), $package, Factory::createConfig($io)); + if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>')) { + $composer->setRepositoryManager($this->mockManager()); + } + + putenv('SYMFONY_REQUIRE='.$constraint); + try { + (new Flex())->activate($composer, $io); + } finally { + putenv('SYMFONY_REQUIRE'); + } + + if ($expectWarning) { + $this->assertStringContainsString(\sprintf('SYMFONY_REQUIRE="%s" is an exact version constraint', $constraint), $io->getOutput()); + } else { + $this->assertStringNotContainsString('is an exact version constraint', $io->getOutput()); + } + } + + public static function getSymfonyRequireConstraints(): array + { + return [ + 'exact major' => ['7', true], + 'exact minor' => ['7.4', true], + 'exact patch' => ['7.4.1', true], + 'caret' => ['^7.4', false], + 'tilde' => ['~7.4', false], + 'wildcard' => ['7.4.*', false], + 'greater or equal' => ['>=7.4', false], + 'x-range' => ['7.4.x', false], + ]; + } + public function testActivateLoadsClasses() { $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE); From 5e9be27a86fb3e45f511f379821fc5c0128a0fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jun 2026 09:58:50 +0200 Subject: [PATCH 2/2] Use Composer Semver VersionParser to detect exact version constraint --- src/Flex.php | 11 ++++++++--- tests/FlexTest.php | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Flex.php b/src/Flex.php index bdecd2d3..5461c3eb 100644 --- a/src/Flex.php +++ b/src/Flex.php @@ -39,6 +39,7 @@ use Composer\Plugin\PrePoolCreateEvent; use Composer\Script\Event; use Composer\Script\ScriptEvents; +use Composer\Semver\Constraint\Constraint; use Composer\Semver\VersionParser; use Symfony\Component\Console\Exception\ExceptionInterface as ConsoleExceptionInterface; use Symfony\Component\Console\Input\ArgvInput; @@ -219,10 +220,14 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) break; } - $symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? '')); + $rawSymfonyRequire = getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''); + $symfonyRequire = preg_replace('/\.x$/', '.x-dev', $rawSymfonyRequire); - if ($symfonyRequire && preg_match('/^\d+(\.\d+)*$/', $symfonyRequire)) { - $io->writeError(\sprintf('SYMFONY_REQUIRE="%s" is an exact version constraint. Did you mean "%s.*" or "^%s"?', $symfonyRequire, $symfonyRequire, $symfonyRequire)); + if ($rawSymfonyRequire) { + $parsedConstraint = (new VersionParser())->parseConstraints($rawSymfonyRequire); + if ($parsedConstraint instanceof Constraint && '==' === $parsedConstraint->getOperator()) { + $io->writeError(\sprintf('SYMFONY_REQUIRE="%s" is an exact version constraint. Did you mean "%s.*" or "^%s"?', $rawSymfonyRequire, $rawSymfonyRequire, $rawSymfonyRequire)); + } } if ($symfonyRequire || $this->ignorePreleases) { diff --git a/tests/FlexTest.php b/tests/FlexTest.php index 2e7b948d..f4c51741 100644 --- a/tests/FlexTest.php +++ b/tests/FlexTest.php @@ -112,7 +112,7 @@ public function testPostInstall() } #[DataProvider('getSymfonyRequireConstraints')] - public function testSymfonyRequireExactVersionWarning(string $constraint, bool $expectWarning): void + public function testSymfonyRequireExactVersionWarning(string $constraint, bool $expectWarning) { $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE);