TransactionEarlyReturn
warning native on by defaultGroup: Probable bugs
Detect a reachable normal return from a scope owning a successfully begun PDO transaction where neither commit nor rollback occurs on the exit path.
Example
php
<?php
function bad(PDO $pdo, bool $skip) { if (!$pdo->beginTransaction()) { return; } if ($skip) { return; } $pdo->commit(); }Reported:
- line 2: Close the owned transaction before returning.
Detection
- Report a reachable normal return from a scope owning a successfully begun PDO transaction where neither commit nor rollback occurs on the exit path.
- 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)
- E1. Ownership transfer, exception unwinding into proven cleanup, surrounding finally cleanup, failure to begin and unknown transaction state 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.
- A return whose own expression closes the transaction is not reported: a
commit()orrollBack()call on the connection that began it must be evaluated every time the expression is, either unconditionally (directly, parenthesised, under a unary operator or cast, as an assigned value, or as an operand that is always evaluated) or in both branches of a full ternary or in every arm of amatch. A call that is only the right operand of&&,||,and,or,??or??=, or one branch of a short ternary, may be skipped and proves nothing, soreturn $saved && $pdo->commit();is still reported. - Surrounding
finallycleanup (E1) covers a directcommit()/rollBack()statement in thefinallyblock and the guarded idiomif ($pdo->inTransaction()) { $pdo->rollBack(); }on the same connection, where the closing call is a direct statement of the guard body. A closing call under any other condition does not prove cleanup. A return written inside thefinallyblock itself is not covered by the guarded idiom.
Fix
No automatic fix: choose commit or rollback explicitly according to the transaction contract.
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:
json
{
"rules": {
"TransactionEarlyReturn": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
php
// @custos-ignore TransactionEarlyReturn