diff --git a/composer.json b/composer.json
index cd7c7a1f66..88e2729105 100644
--- a/composer.json
+++ b/composer.json
@@ -9,6 +9,7 @@
"composer-runtime-api": "^2.0",
"clue/ndjson-react": "^1.0",
"composer/ca-bundle": "^1.2",
+ "composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.3",
"fidry/cpu-core-counter": "^0.5.0",
"hoa/compiler": "3.17.08.08",
diff --git a/composer.lock b/composer.lock
index 8ccc5c9d9f..37783b1e98 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "15c269b06d7fb91ea60c99e978d51483",
+ "content-hash": "fa85814150e9cc7a232f2da08daec7b7",
"packages": [
{
"name": "clue/ndjson-react",
@@ -217,6 +217,87 @@
],
"time": "2024-03-19T10:26:25+00:00"
},
+ {
+ "name": "composer/semver",
+ "version": "3.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
+ "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.4",
+ "symfony/phpunit-bridge": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/semver/issues",
+ "source": "https://github.com/composer/semver/tree/3.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-08-31T09:50:34+00:00"
+ },
{
"name": "composer/xdebug-handler",
"version": "3.0.5",
diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php
index 32fd7bae98..d38aee7f23 100644
--- a/src/Command/CommandHelper.php
+++ b/src/Command/CommandHelper.php
@@ -2,6 +2,7 @@
namespace PHPStan\Command;
+use Composer\Semver\Semver;
use Composer\XdebugHandler\XdebugHandler;
use Nette\DI\Helpers;
use Nette\DI\InvalidConfigurationException;
@@ -23,6 +24,8 @@
use PHPStan\File\FileExcluder;
use PHPStan\File\FileFinder;
use PHPStan\File\FileHelper;
+use PHPStan\File\SimpleRelativePathHelper;
+use PHPStan\Internal\ComposerHelper;
use PHPStan\Internal\DirectoryCreator;
use PHPStan\Internal\DirectoryCreatorException;
use PHPStan\PhpDoc\StubFilesProvider;
@@ -279,6 +282,43 @@ public static function begin(
$additionalConfigFiles[] = $includedFilePath;
}
}
+
+ if (
+ count($additionalConfigFiles) > 0
+ && $generatedConfigReflection->hasConstant('PHPSTAN_VERSION_CONSTRAINT')
+ ) {
+ $generatedConfigPhpStanVersionConstraint = $generatedConfigReflection->getConstant('PHPSTAN_VERSION_CONSTRAINT');
+ if ($generatedConfigPhpStanVersionConstraint !== null) {
+ $phpstanSemverVersion = ComposerHelper::getPhpStanVersion();
+ if (
+ $phpstanSemverVersion !== ComposerHelper::UNKNOWN_VERSION
+ && !str_contains($phpstanSemverVersion, '@')
+ && !Semver::satisfies($phpstanSemverVersion, $generatedConfigPhpStanVersionConstraint)
+ ) {
+ $errorOutput->writeLineFormatted('Running PHPStan with incompatible extensions');
+ $errorOutput->writeLineFormatted('You\'re running PHPStan from a different Composer project');
+ $errorOutput->writeLineFormatted('than the one where you installed extensions.');
+ $errorOutput->writeLineFormatted('');
+ $errorOutput->writeLineFormatted(sprintf('Your PHPStan version is: %s>', $phpstanSemverVersion));
+ $errorOutput->writeLineFormatted(sprintf('Installed PHPStan extensions support: %s', $generatedConfigPhpStanVersionConstraint));
+
+ $errorOutput->writeLineFormatted('');
+ if (isset($_SERVER['argv'][0]) && is_file($_SERVER['argv'][0])) {
+ $mainScript = $_SERVER['argv'][0];
+ $errorOutput->writeLineFormatted(sprintf('PHPStan is running from: %s', $currentWorkingDirectoryFileHelper->absolutizePath(dirname($mainScript))));
+ }
+
+ $errorOutput->writeLineFormatted(sprintf('Extensions were installed in: %s', dirname($generatedConfigDirectory, 3)));
+ $errorOutput->writeLineFormatted('');
+
+ $simpleRelativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory);
+ $errorOutput->writeLineFormatted(sprintf('Run PHPStan with %s> to fix this problem.', $simpleRelativePathHelper->getRelativePath(dirname($generatedConfigDirectory, 3) . '/bin/phpstan')));
+
+ $errorOutput->writeLineFormatted('');
+ throw new InceptionNotSuccessfulException();
+ }
+ }
+ }
}
if (
diff --git a/src/Internal/ComposerHelper.php b/src/Internal/ComposerHelper.php
index 322d7f1048..e1995bc34d 100644
--- a/src/Internal/ComposerHelper.php
+++ b/src/Internal/ComposerHelper.php
@@ -17,6 +17,8 @@
final class ComposerHelper
{
+ public const UNKNOWN_VERSION = 'Unknown version';
+
private static ?string $phpstanVersion = null;
/** @return array|null */
@@ -75,7 +77,7 @@ public static function getPhpStanVersion(): string
$installed = require __DIR__ . '/../../vendor/composer/installed.php';
$rootPackage = $installed['root'] ?? null;
if ($rootPackage === null) {
- return self::$phpstanVersion = 'Unknown version';
+ return self::$phpstanVersion = self::UNKNOWN_VERSION;
}
if (preg_match('/[^v\d.]/', $rootPackage['pretty_version']) === 0) {