Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Put the PHP version the analysis targets into the result cache metadata
The metadata recorded PHP_VERSION_ID, the version PHPStan runs on, and
nothing else about versions. The version the analysis targets is a different
number: it comes from the phpVersion parameter, which is part of
projectConfig, or from config.platform.php in composer.json, which is hashed
nowhere. composerLocks and composerInstalled cover composer.lock and
installed.php, and neither of those moves when composer.json alone changes -
its config and autoload keys are not part of Composer's lock content hash
either. Lowering config.platform.php from 8.4 to 7.4 therefore left the cache
untouched while a run from a cleared cache reported the functions that do not
exist on 7.4, and raising it back kept the errors.

The PhpVersion service the rest of PHPStan reads is now injected here and its
version and source go into the metadata, together with the version range
ComposerPhpVersionFactory derives from composer.json's require section - the
same input, reaching the analysis through a different door.

e2e/result-cache-composer-json changes only config.platform.php and expects
the error a cleared cache reports.
  • Loading branch information
ondrejmirtes committed Sep 3, 2026
commit b7cb6864296608498131a959fba492b8d03f0dfa
12 changes: 12 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,18 @@ jobs:
echo "$OUTPUT"
../bashunit -a contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT"
../bashunit -a contains 'UsesCopied.php:16:Method ResultCacheE2EPathRepository\UsesCopied::doUsesCopied() should return int but returns string.' "$OUTPUT"
- script: |
cd e2e/result-cache-composer-json
composer install
../../bin/phpstan analyse
# composer.json decides which PHP version the analysis targets (config.platform.php) and
# which symbols the autoloader can reach, but its contents are hashed nowhere in the cache
# metadata - composerLocks and composerInstalled cover composer.lock and installed.php, and
# neither of those moves when only composer.json changes.
patch -p0 < platform.patch
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Foo.php:10:Function str_contains not found.' "$OUTPUT"
- script: |
cd e2e/result-cache-extension-update
composer install
Expand Down
2 changes: 2 additions & 0 deletions e2e/result-cache-composer-json/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/vendor
/composer.lock
11 changes: 11 additions & 0 deletions e2e/result-cache-composer-json/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "phpstan/result-cache-composer-json-e2e",
"autoload": {
"classmap": ["src"]
},
"config": {
"platform": {
"php": "8.4.0"
}
}
}
5 changes: 5 additions & 0 deletions e2e/result-cache-composer-json/phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
level: 0
tmpDir: tmp
paths:
- src
11 changes: 11 additions & 0 deletions e2e/result-cache-composer-json/platform.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--- composer.json
+++ composer.json
@@ -5,7 +5,7 @@
},
"config": {
"platform": {
- "php": "8.4.0"
+ "php": "7.4.0"
}
}
}
13 changes: 13 additions & 0 deletions e2e/result-cache-composer-json/src/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

namespace ResultCacheE2EComposerJson;

class Foo
{

public function doFoo(string $s): bool
{
return str_contains($s, 'a');
}

}
2 changes: 2 additions & 0 deletions e2e/result-cache-composer-json/tmp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.*
19 changes: 18 additions & 1 deletion src/Analyser/ResultCache/ResultCacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
use PHPStan\File\FileHelper;
use PHPStan\Internal\ArrayHelper;
use PHPStan\Internal\ComposerHelper;
use PHPStan\Php\ComposerPhpVersionFactory;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDoc\StubFilesProvider;
use PHPStan\ShouldNotHappenException;
use ReflectionClass;
Expand Down Expand Up @@ -84,7 +86,7 @@ final class ResultCacheManager
*/
private const EXTENSIONS_NOT_INVALIDATING_CACHE = ['xdebug', 'blackfire', 'phpstan_turbo'];

private const CACHE_VERSION = 'v16-nonAnalysedExportedNodes';
private const CACHE_VERSION = 'v17-phpVersionForAnalysis';

private const SCANNED_FILE_APPEARED = 'appeared';
private const SCANNED_FILE_EDITED = 'edited';
Expand Down Expand Up @@ -159,6 +161,8 @@ public function __construct(
private int $skipResultCacheIfOlderThanDays,
#[AutowiredParameter(ref: '%rootDir%')]
private string $anchorDirectory,
private PhpVersion $phpVersion,
private ComposerPhpVersionFactory $composerPhpVersionFactory,
)
{
}
Expand Down Expand Up @@ -1760,11 +1764,24 @@ private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): a
// __DIR__ . '/../x', --autoload-file may be given as ./vendor/autoload.php. Normalizing them
// here makes the comparison one of paths, not of spellings - otherwise such an entry reads
// as a metadata change on every run and the whole cache is discarded every time.
$composerMinPhpVersion = $this->composerPhpVersionFactory->getMinVersion();
$composerMaxPhpVersion = $this->composerPhpVersionFactory->getMaxVersion();

return $this->getPathTransformer()->normalizeMeta([
'cacheVersion' => self::CACHE_VERSION,
'phpstanVersion' => ComposerHelper::getPhpStanVersion(),
'metaExtensions' => $this->getMetaFromPhpStanExtensions(),
'phpVersion' => PHP_VERSION_ID,
// The version the analysis targets, which is not the one PHPStan runs on: it can come from
// the phpVersion parameter, or from config.platform.php in composer.json. The parameter is
// part of projectConfig, but composer.json is hashed nowhere - and the two version ranges
// below come from its require section, which is not in composer.lock's content hash either,
// so a composer update need not move anything else in this metadata.
'phpVersionForAnalysis' => [$this->phpVersion->getVersionId(), $this->phpVersion->getSource()],
'composerPhpVersionRange' => [
$composerMinPhpVersion !== null ? $composerMinPhpVersion->getVersionId() : null,
$composerMaxPhpVersion !== null ? $composerMaxPhpVersion->getVersionId() : null,
],
'projectConfig' => $projectConfigArray,
'analysedPaths' => $this->analysedPaths,
'scannedFiles' => $this->getScannedFiles($allAnalysedFiles),
Expand Down