HeadersAfterCommittedOutput
warning native on by defaultGroup: Probable bugs
Detect a response-header write (header, setcookie, or http_response_code used as a setter) after demonstrably committed output on every incoming path, such as output plus flush with no active buffering.
Example
<?php
echo 'started'; flush(); header('X-State: ready');Reported:
- line 2: Set response headers before committing output.
Detection
- Report
header,setcookie, orhttp_response_codecalled as a setter, after demonstrably committed output on every incoming path, such as output plus flush with no active buffering. http_response_codeis a setter only when it receives a status argument (first positional or namedresponse_code) that is not a known falsy value. A dynamic status counts as a setter.- Resolve builtin calls and named arguments before checking the contract. Project-local callees are considered only when their bodies or immutable summaries establish the required facts. The default is enabled, with positive proof required.
- For flow-dependent conditions, require a reachable source-to-use path with no intervening invalidation. Unknown calls, ambiguous aliases, recursion or analysis-budget exhaustion cannot establish definite facts.
Exceptions (no report)
Active output buffering, unknown server buffering, no preceding output and headers before commitment are excluded.
Unresolved targets, incomplete required facts, suppressed findings and PHP versions lacking the referenced API or language feature produce no report. User declarations shadowing builtin names are not builtin contracts.
http_response_code()without a status argument, with a known0ornullstatus, or with an unpacked argument list: the first two only read the current status, the last is not provably a write.Calls that a
headers_sent()test keeps off the committed path: an earlier call to the builtin in the same function scope whose result the conditions guarding the call prove false. That covers:- the body of
if (!headers_sent()),while (!headers_sent())or anelseifwith that condition, and theelseofif (headers_sent()); - code after an
if (headers_sent())whose body always leaves, with or without other arms; - the right operand of
headers_sent() || .../or, or of!headers_sent() && .../and; - the matching branch of a ternary or
match (true)arm on such a test.
The test may be parenthesised, negated, compared with
true/false(===,==,!==,!=), one operand of a conjunction that must hold or of a disjunction that must fail, or kept in a local read later. The exception does not apply when the committing flush sits between the test and the call, because the test result is then stale.- the body of
Fix
No automatic fix: moving headers can change control flow and response semantics.
PHP versions
Apply only on PHP versions supporting the referenced APIs and syntax (within PHP 5.3–8.5). Builtin behavior and argument contracts follow the configured PHP version. Extension-specific calls require resolved extension API symbols; do not treat same-named application functions as extension calls.
Options
None. Enable or disable this rule through normal rule configuration.
Configure
In custos.json:
{
"rules": {
"HeadersAfterCommittedOutput": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
// @custos-ignore HeadersAfterCommittedOutput