Skip to content

TransactionEarlyReturn ​

warning native on by default

Group: 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() or rollBack() 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 a match. 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, so return $saved && $pdo->commit(); is still reported.
  • Surrounding finally cleanup (E1) covers a direct commit()/rollBack() statement in the finally block and the guarded idiom if ($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 the finally block 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

Released under the MIT License. Native rules and rules modeled on Php Inspections (EA Extended); independent clean-room implementation.