bind_param()
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and semgrep as the tools, and automated detection is marked yes — these are specialist static analysis tools rather than default linters or compilers. The code_pattern notes type mismatches and wrong binding contexts, which require semantic analysis beyond a basic linter.
Closest to 'simple parameterised fix' (e3). The quick_fix states switching to PDO's bindValue() or restructuring to PDO named placeholders — this is a pattern-level replacement within a component (swap mysqli prepared statements for PDO equivalents) rather than a single-line patch or an architectural rework. It may touch multiple queries but is locally contained.
Closest to 'localised tax' (b3). bind_param() applies across web, cli, and queue-worker contexts per applies_to, but its reach is limited to the data-access layer. It doesn't bleed into every part of the codebase — only code that interacts directly with the database bears the tax. Future maintainers need to understand the reference semantics but it doesn't reshape the whole system.
Closest to 'serious trap' (t7). The misconception field explicitly states that bind_param() binds by reference (value read at execute() time), directly contradicting PDO's bindValue() which captures immediately. Developers familiar with PDO will confidently apply the wrong mental model, causing subtle loop-binding bugs. This is a documented behavioural contradiction across similar concepts in the same ecosystem, warranting t7.
Also Known As
TL;DR
Explanation
mysqli_stmt::bind_param(string $types, mixed &...$vars) associates PHP variables with the ? placeholders in a prepared MySQLi statement. The type string must have one character per variable: s (string), i (integer), d (double/float), b (blob). Values are transmitted to the database separately from the query, making injection impossible. Must be called after prepare() and before execute().
Common Misconception
Why It Matters
Common Mistakes
- Using the wrong type character: 's' for string, 'i' for integer, 'd' for double, 'b' for blob — a mismatch causes incorrect binding.
- Not understanding that bind_param() binds by reference — changing the variable after binding changes what gets sent.
- Using bind_param() with a dynamic number of parameters without call_user_func_array() or spread — directly concatenating SQL is SQLi.
- Choosing mysqli over PDO for new code — PDO named placeholders (:name) are clearer and less error-prone than positional binding.
Code Examples
// Wrong type string — 's' used for integer column:
$stmt = $conn->prepare('SELECT * FROM users WHERE id = ?');
$stmt->bind_param('s', $id); // Should be 'i' for integer
// Dynamic params without spread — common but verbose:
$types = str_repeat('s', count($values));
$stmt->bind_param($types, ...$values);
// Type chars: s=string, i=integer, d=double, b=blob
\$stmt = \$db->prepare('INSERT INTO products (name, price, stock) VALUES (?, ?, ?)');
\$stmt->bind_param('sdi', \$name, \$price, \$stock);
\$name = 'Widget';
\$price = 9.99;
\$stock = 100;
\$stmt->execute();
// Reuse same statement for multiple rows:
\$stmt = \$db->prepare('INSERT INTO tags (name) VALUES (?)');
\$stmt->bind_param('s', \$tag);
foreach (['php', 'security', 'mysql'] as \$tag) {
\$stmt->execute(); // \$tag bound by reference — updates each iteration
}
\$stmt->close();