PDOStatement::bindParam() vs bindValue()
debt(d5/e1/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list PHPStan as the tool, and the code_pattern shows PHPStan can flag bindParam() called with literals or expressions. This is not a compiler/syntax error nor a default linter catch — it requires a static analysis tool like PHPStan configured for PDO analysis.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix explicitly states: use execute([$val1, $val2]) for most cases, or swap bindParam() for bindValue(). Both fixes are single-call replacements at the call site.
Closest to 'localised tax' (b3). The applies_to covers web and cli contexts broadly, but the actual burden is localised to database query code. Once corrected, the rest of the codebase is unaffected. It does not impose a systemic cross-cutting tax.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field states developers believe bindParam() and bindValue() are interchangeable, but bindParam() binds by reference and evaluates at execute() time — the opposite of what most developers expect. This contradicts intuition from similar parameter binding APIs in other languages/ORMs and causes silent wrong-value bugs in loops, making it a serious behavioural trap.
Also Known As
TL;DR
Explanation
bindParam($placeholder, $var, $type) binds a variable by reference — the current value at execute() time is used. bindValue($placeholder, $val, $type) binds the value immediately — changing $val later has no effect. bindParam() is useful in loops where the variable changes between iterations. The $type parameter (PDO::PARAM_INT, PDO::PARAM_STR, PDO::PARAM_BOOL, PDO::PARAM_NULL) enforces type safety. Most developers use execute([$val]) shorthand — equivalent to bindValue for most cases.
Common Misconception
Why It Matters
Common Mistakes
- Using bindParam() with a loop variable that doesn't change — execute() always sees the same value.
- Passing PDO::PARAM_INT for a column that stores NULL — use PDO::PARAM_NULL or check before binding.
- Over-using explicit bind calls when execute([]) is cleaner and sufficient.
Avoid When
- Do not use bindParam() with a literal value or expression — PHP cannot bind non-variables by reference.
- Avoid explicit bind calls when execute([$val]) shorthand is cleaner — they are equivalent for most cases.
When To Use
- Use bindParam() when reusing a prepared statement in a loop with a variable that changes each iteration.
- Use bindValue() with PDO::PARAM_INT when type safety matters for the query optimiser.
Code Examples
// bindParam on literal/expression — PHP warning
$stmt->bindParam(':id', 42); // Cannot bind non-variable by reference
$stmt->bindParam(':id', $a + $b); // Same issue
// execute([]) shorthand — cleaner for simple cases
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$userId]);
// bindParam in loop — variable changes each iteration
$stmt = $pdo->prepare('INSERT INTO tags (name) VALUES (?)');
$stmt->bindParam(1, $tagName); // bound by reference
foreach ($tags as $tagName) {
$stmt->execute(); // $tagName value used at each execute()
}
// bindValue with type
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->bindValue(1, $userId, PDO::PARAM_INT);
$stmt->execute();