Make the analysis web UI non-blocking - #1067
Open
skerbis wants to merge 2 commits into
Open
Conversation
…output
RexStan::generateAnalysisBaseline() and analyzeSummaryBaseline() (used
by the "ignore all" button on the analysis page and by the summary
page respectively) invoke PHPStan without --no-progress, unlike every
other PHPStan invocation in this file.
Depending on environment, PHPStan's progress-bar control characters
end up interleaved into the captured stderr output. On failure that
raw output is placed directly into the thrown exception's message
("Unable to generate baseline: <progress bar escape codes>"), making
the actual underlying error unreadable - encountered this while a
Nette container-cache directory had gone stale, where the real cause
("Unable to create file ...") was buried under garbled control
characters.
Both call sites now pass --no-progress, matching every other PHPStan
invocation already in this file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pages/analysis.php previously ran RexStan::runFromWeb() synchronously,
blocking the whole page request for however long the PHPStan run took
(seconds to minutes on larger codebases), with the attendant risk of
hitting max_execution_time or a reverse-proxy timeout.
The page now loads instantly:
- if a background run is already in progress, it shows a spinner and
starts polling immediately;
- otherwise it shows the last cached result (if any) plus a "re-run"
button;
- on the very first run on a system (no cache yet), it kicks off a
background run automatically.
New pieces:
- RexStanRunStore: file-based state (lock/result/error-log) shared
between the detached background process and the polling requests.
A lock older than 15 minutes is treated as an orphaned/crashed run
rather than an active one, so a dead background process can never
permanently block future runs.
- RexStan::startBackgroundWebAnalysis(): spawns the same phpstan
invocation runFromWeb() already used, detached from the request
(Unix: `shell_exec('(...) &')`, Windows: `start /B`). The result is
written to a temp file first and renamed into place afterwards, so a
poller can never observe a partially-written result.
- Api\AnalysisApi: the ajax endpoint backing start/status polling,
registered as "rexstan_analysis". Mirrors the
ob_start()+sendJsonClean() stray-output guard and the
session_write_close()-before-long-running-work pattern used
elsewhere in this ecosystem for the same reasons.
- RexResultsRenderer::renderAnalysisBody(): the rendering logic that
used to live directly in pages/analysis.php, extracted so both the
page (cached result) and the status endpoint (fresh result) render
identical markup.
- assets/rexstan-analysis.js: vanilla JS (no jQuery dependency)
driving the start/poll/swap flow.
Also extracted RexStan::interpretAnalysisOutput() (the JSON-vs-plain-
text interpretation of PHPStan's raw output) out of runFromWeb() so
both the synchronous and the new background path share it, and fixed
a pre-existing PSR-3 log-interpolation finding on the line it moved.
Known limitations (see CHANGELOG): no manual cancel of a running
background analysis yet; no fallback to the old synchronous behavior
on hosts where shell_exec()/proc_open() is unavailable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Recreated from #1061 (closed) as part of flattening what had grown into a 5-deep PR stack (#1061→#1065) into a small set of independent PRs. This PR's content is unchanged from #1061 — only the PR itself is fresh, so it isn't tied into GitHub's native stacked-PR tracking that blocked retargeting individual PRs later on.
pages/analysis.phppreviously ranRexStan::runFromWeb()synchronously, blocking the whole page request for however long the PHPStan run took (seconds to minutes on a larger codebase), with the attendant risk of hittingmax_execution_timeor a reverse-proxy timeout.The page now loads instantly:
New pieces:
RexStanRunStore: file-based state (lock/result/error-log) shared between the detached background process and the polling requests. A lock older than 15 minutes is treated as an orphaned/crashed run rather than an active one, so a dead background process can never permanently block future runs.RexStan::startBackgroundWebAnalysis(): spawns the same PHPStan invocationrunFromWeb()already used, detached from the request (Unix:shell_exec('(...) &'), Windows:start /B). The result is written to a temp file first and renamed into place afterwards, so a poller can never observe a partially-written result.Api\AnalysisApi: the ajax endpoint backing start/status polling, registered asrexstan_analysis. Mirrors theob_start()+clean-JSON-response guard and thesession_write_close()-before-long-running-work pattern used elsewhere in this ecosystem for the same reasons.RexResultsRenderer::renderAnalysisBody(): the rendering logic that used to live directly inpages/analysis.php, extracted so both the page (cached result) and the status endpoint (fresh result) render identical markup.Also extracted
RexStan::interpretAnalysisOutput()(the JSON-vs-plain-text interpretation of PHPStan's raw output) out ofrunFromWeb()so both the synchronous and the new background path share it, and fixed a pre-existing PSR-3 log-interpolation finding on the line it moved.Two independent, non-stacked follow-up PRs build on top of this one (base = this branch, not on each other):
Known limitations
shell_exec()/proc_open()is unavailable.Test plan
isRunning()correctly reflects state, double-start is rejected, background completion is detected, atomic rename verified.phpstan analyseinvocation (ruling out any shortcut/staleness).php -lon all changed/added files; PHPStan on all new/changed files: 0 findings.🤖 Generated with Claude Code