Skip to content

Add a result-cache-info command reporting how the next analyse would use the result cache - #6312

Merged
ondrejmirtes merged 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-fru1428
Aug 31, 2026
Merged

ondrejmirtes merged 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-fru1428

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

The issue asks for a way to inspect the result cache state before running an analysis, so a project can decide whether it is worth downloading a result cache artifact built in CI. Two options were floated; in the discussion @ondrejmirtes settled on a result-cache-info command that "verifies how analyse would approach the analysis if nothing else changed between running the two commands", with human output first and --json for machine-readable output.

This adds that command. It runs the same inception and file discovery as analyse, feeds the discovered files through ResultCacheManager::restore(), and then stops - nothing is analysed. It reports whether the result cache will be used at all, why not when it will not, and how many files out of how many would be analysed.

$ bin/phpstan result-cache-info
Result cache file: /project/tmp/resultCache.php
Result cache will be used.
Last full analysis: 2026-08-31 08:01:01
1 out of 1234 files will be analysed.

$ bin/phpstan result-cache-info --json
{
    "resultCachePath": "/project/tmp/resultCache.php",
    "resultCacheExists": true,
    "resultCacheUsed": false,
    "notUsedReason": "Result cache not used because the metadata do not match: level",
    "analysedFilesCount": 1234,
    "filesToAnalyseCount": 1234,
    "lastFullAnalysisTime": null
}

Changes

  • src/Command/ResultCacheInfoCommand.php (new) - the result-cache-info command. Takes the same paths argument and --configuration/--level/--autoload-file/--memory-limit/--debug/--xdebug options as analyse, plus --json and --fail-without-result-cache.
    • Uses deferBootstrapFiles: true like analyse does, because analyse also restores the result cache before the bootstrap files run - so the computed meta is identical.
    • --debug is deliberately not forwarded to restore(): debug mode always disables the result cache, and the command would then have nothing to report.
    • --fail-without-result-cache returns exit code 2 when the cache would not be used, reusing the option name and exit code that analyse already has for the same question. Without it the command always exits 0 on success.
  • bin/phpstan - registers the new command.
  • src/Analyser/ResultCache/ResultCache.php - new fullAnalysisReason constructor parameter and getFullAnalysisReason(); null when the result cache is being used.
  • src/Analyser/ResultCache/ResultCacheManager.php - new private fullAnalysis() helper, replacing the twelve copies of the if ($output->isVeryVerbose()) { ... } return new ResultCache(fullAnalysis: true, ...) block. The helper prints the reason in very verbose mode (unchanged behaviour) and records it on the ResultCache. Net effect on that file: 235 lines removed, 127 added.

Analogous cases considered:

  • All twelve "result cache not used" branches in restore() were swept in one go, not just the ones easy to hit: debug mode, only-files, missing cache file, unreadable cache file, corrupted cache file, meta mismatch, changed Composer packages that register a container class, cache older than resultCacheSkipIfOlderThanDays, missing extension file, changed extension file hash, missing stub file, changed stub file hash. Every one of them now reports its reason through getFullAnalysisReason().
  • The sibling "result cache was not saved" reasons in ResultCacheManager::process()/doSave() are the obvious parallel family. They were probed and deliberately left alone: they are only knowable after an analysis has run, so result-cache-info cannot report them and threading a reason through ResultCacheProcessResult would add an unused API.
  • The other restore() callers (AnalyseApplication, FixerWorkerRunner) were checked - both use named arguments and are unaffected by the new constructor parameter.

Root cause

Not a bug - a missing capability. The information the issue asks for already exists inside ResultCacheManager::restore(), but it was only reachable in two ways: by running a full analysis (analyse --fail-without-result-cache, which only fails after the analysis) or by reading very verbose log lines. The reason string in particular was written straight to the output and then thrown away, which is why it was duplicated twelve times next to twelve identical new ResultCache(...) calls. Turning the reason into a value on ResultCache both removes that duplication and makes the information available to a command that does not analyse anything.

Test

tests/PHPStan/Command/ResultCacheInfoCommandTest.php (new, exec group, so it runs under make tests-integration) builds a throwaway two-file project in a temp directory with its own tmpDir and drives the real bin/phpstan binary through exec():

  • no cache file yet: resultCacheExists: false, resultCacheUsed: false, reason "Result cache not used because the cache file does not exist.", 2 out of 2 files; and exit code 2 with --fail-without-result-cache
  • after analyse: resultCacheUsed: true, 0 out of 2 files, a lastFullAnalysisTime timestamp, and exit code 0 with --fail-without-result-cache
  • after touching one source file: still used, 1 out of 2 files
  • with -l 6 against a level-5 cache: not used, reason reports the level meta difference
  • human-readable output for both the "will not be used" and "will be used" cases

All five fail without the change (the command does not exist), verified by reverting the bin/phpstan registration. Full make tests, make tests-integration, make phpstan and make cs are green.

Fixes phpstan/phpstan#15135

…uld use the result cache

- New `PHPStan\Command\ResultCacheInfoCommand` (`result-cache-info`), registered in `bin/phpstan`. It performs the same inception and file discovery as `analyse`, runs `ResultCacheManager::restore()` and then stops, so it answers whether the result cache would be used and how many files out of how many would be analysed - without analysing anything.
- Human-readable output by default, `--json` for machine-readable output (`resultCachePath`, `resultCacheExists`, `resultCacheUsed`, `notUsedReason`, `analysedFilesCount`, `filesToAnalyseCount`, `lastFullAnalysisTime`).
- `--fail-without-result-cache` makes the command exit with code 2 when the cache would not be used, matching the option name and exit code already used by `analyse`.
- `ResultCache` carries a new `fullAnalysisReason` (`getFullAnalysisReason()`), so the reason the cache could not be reused is available to callers instead of only being printed in very verbose mode.
- `ResultCacheManager::restore()`: extracted the twelve duplicated `return new ResultCache(fullAnalysis: true, ...)` blocks into a single private `fullAnalysis()` helper that both prints the reason in very verbose mode and stores it on the result. All twelve reasons (debug mode, only files, missing cache file, unreadable cache file, corrupted cache file, meta mismatch, changed Composer packages registering container classes, cache too old, missing/changed extension file, missing/changed stub file) are covered - the same sweep across the whole family.
@ondrejmirtes
ondrejmirtes merged commit b27bd43 into phpstan:2.2.x Aug 31, 2026
764 of 771 checks passed
@ondrejmirtes
ondrejmirtes deleted the create-pull-request/patch-fru1428 branch August 31, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Using result cache state to operate PHPStan

2 participants