Question
What is the difference between PDOStatement::bindParam() and PDOStatement::bindValue() in PHP PDO? When should each method be used when binding values to prepared-statement placeholders?
Short Answer
You will learn how PHP PDO binds data to prepared SQL statements, why bindParam() uses a variable reference, why bindValue() stores a value immediately, and how that difference affects repeated calls to execute().
Concept
Prepared statements separate SQL code from the data supplied to it. First, you prepare SQL containing placeholders such as :email; then, you bind PHP data to those placeholders before executing the statement.
Both methods bind data, but they do so at different times:
bindParam()binds a variable by reference. PDO reads that variable's current value whenexecute()runs.bindValue()binds the value immediately when you callbindValue().
This distinction matters when a statement is executed more than once. With bindParam(), you can change the bound variable before each execution. With bindValue(), changing the original variable later does not change the already-bound value.
Both methods support named placeholders such as :id and positional placeholders such as ?. They also let you specify a PDO type, for example PDO::PARAM_INT for integers.
Mental Model
Think of a prepared statement as a form with blank fields.
bindValue()takes a photograph of what you provide now. If the original value changes later, the photograph does not change.bindParam()attaches a live label to a variable. When the form is submitted withexecute(), PDO checks the variable's current contents.
Use the photograph (bindValue) when you have the final value. Use the live label (bindParam) when you intentionally want to reuse one statement while changing variables between executions.
Syntax and Examples
bindValue() accepts a value directly:
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE email = :email AND active = :active'
);
$stmt->bindValue(':email', 'ada@example.com', PDO::PARAM_STR);
$stmt->bindValue(':active', true, PDO::PARAM_BOOL);
$stmt->execute();
The string and boolean are stored when bindValue() is called. You may pass a literal, an expression, or a variable.
bindParam() requires a variable because PHP must pass that variable by reference:
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$userId = 42;
$stmt->bindParam(':id', $userId, PDO::PARAM_INT);
$stmt->execute();
In this example, PDO reads the current value of $userId when execute() runs.
A useful repeated-execution example:
$stmt = $pdo->prepare(
'INSERT INTO subscribers (email) VALUES (:email)'
);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
foreach (['a@example.com', 'b@example.com'] as $email) {
$stmt->execute();
}
Each execution uses the new value assigned to $email in the loop.
Step by Step Execution
Consider this code:
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = :id');
$productId = 10;
$stmt->bindParam(':id', $productId, PDO::PARAM_INT);
$productId = 25;
$stmt->execute();
Step by step:
prepare()creates a statement with a named placeholder,:id.$productIdis initially10.bindParam()connects:idto the variable$productIdby reference. It does not permanently store10at this point.$productIdchanges to25.execute()reads the current referenced value, so the query receives25.
Now compare bindValue():
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = :id');
$productId = 10;
$stmt->bindValue(':id', $productId, PDO::PARAM_INT);
$productId = 25;
$stmt->execute();
Here, bindValue() stores 10 during step 3. The later assignment to 25 does not alter the bound value, so the query receives 10.
Real World Use Cases
- Single database lookup: Use
bindValue()when handling a request such asGET /products/42, where you already have the final ID. - Form submission: Bind submitted fields such as an email address, title, or password hash with
bindValue()before inserting or updating a row. - Batch imports: Use
bindParam()to prepare oneINSERTstatement and change bound variables for every CSV row. - Data migrations: Reuse a prepared
UPDATEstatement while iterating through many records and assigning new values before each execution. - Typed API filters: Use
PDO::PARAM_INT,PDO::PARAM_BOOL, orPDO::PARAM_NULLto make the intended SQL value type clear.
Real Codebase Usage
In application code, bindValue() is often the clearest choice for one-off queries because it binds the final data explicitly.
function findUserByEmail(PDO $pdo, string $email): array|false
{
$stmt = $pdo->prepare(
'SELECT id, email, name FROM users WHERE email = :email'
);
$stmt->bindValue(':email', $email, PDO::PARAM_STR);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
For batch work, bindParam() can avoid repeatedly binding each placeholder:
$stmt = $pdo->prepare(
'UPDATE inventory SET quantity = :quantity WHERE sku = :sku'
);
$stmt->bindParam(':quantity', $quantity, PDO::PARAM_INT);
$stmt->bindParam(':sku', $sku, PDO::PARAM_STR);
foreach ($updates as $update) {
$quantity = $update['quantity'];
$sku = $update['sku'];
$stmt->execute();
}
In real projects, also validate input before binding it. Prepared statements protect the SQL structure from injection, but they do not decide whether a value is logically valid. For example, validate that a required email is present or that a quantity is non-negative.
Common Mistakes
Passing a literal to bindParam()
This is invalid because bindParam() needs a variable it can reference:
// Incorrect
$stmt->bindParam(':id', 42, PDO::PARAM_INT);
Use bindValue() for literals:
$stmt->bindValue(':id', 42, PDO::PARAM_INT);
Or assign the value to a variable first:
$id = 42;
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
Expecting bindValue() to track later changes
$id = 1;
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$id = 2;
$stmt->execute(); // Still uses 1
Call bindValue() again after changing the value, or use bindParam() if late binding is intended.
Forgetting that bindParam() reads at execution time
$id = 1;
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$id = 2;
$stmt->execute(); // Uses 2, not 1
Choose bindValue() if you need to preserve the value as it was when bound.
Binding an integer as a string unintentionally
PDO commonly works without explicit types, but specifying types improves clarity and avoids surprises in type-sensitive queries:
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
Trying to bind an identifier
Placeholders represent data values, not SQL keywords, table names, or column names:
// This cannot safely choose a table name as intended.
$stmt = $pdo->prepare('SELECT * FROM :table');
Use a fixed allowlist for dynamic SQL identifiers, then place the approved identifier into the SQL string. Continue using placeholders for actual data values.
Comparisons
| Feature | bindParam() | bindValue() |
|---|---|---|
| What it binds | A variable by reference | A value at the time of the call |
Value used by execute() | The variable's current value | The value already stored |
| Accepts literals and expressions | No | Yes |
| Best for | Repeated execution with changed variables | One-off queries and final values |
| Can specify a PDO type | Yes | Yes |
execute() can also receive an array for straightforward one-time execution:
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute([':id' => 42]);
This is concise, but explicit bindValue() can be preferable when you want to document types such as PDO::PARAM_INT or PDO::PARAM_BOOL.
Cheat Sheet
// Store the value now
$stmt->bindValue(':name', 'Ada', PDO::PARAM_STR);
// Reference a variable; read its value during execute()
$name = 'Ada';
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
- Use
bindValue()for literals, expressions, and most one-time queries. - Use
bindParam()only with variables. - Change a
bindParam()variable beforeexecute()to use its new value. - Rebind with
bindValue()if the desired value changes. - Use
PDO::PARAM_INTfor integers,PDO::PARAM_BOOLfor booleans,PDO::PARAM_NULLfornull, andPDO::PARAM_STRfor strings. - Placeholders bind data values, not table names, column names, or SQL syntax.
- Always execute prepared statements rather than concatenating untrusted input into SQL.
FAQ
Should I use bindParam() or bindValue()?
Use bindValue() for most one-time queries. Use bindParam() when you deliberately reuse a prepared statement and update the same variables before each execute().
Why does bindParam() require a variable?
It binds by reference. PDO needs a variable whose value it can read later when the statement executes.
Can I pass a string literal to bindParam()?
No. Use bindValue(':status', 'active'), or assign 'active' to a variable and bind that variable with bindParam().
Does bindValue() prevent SQL injection?
Using either binding method with prepared statements keeps values separate from SQL structure, which helps prevent SQL injection. Input validation is still necessary for business rules and data quality.
What happens if I change a variable after bindValue()?
The bound value does not change. bindValue() captured the value at the moment you called it.
Do I always need to provide PDO::PARAM_INT or another type?
No, but supplying a type is useful when the intended type matters, especially for integers, booleans, and null values.
Can I use the same named placeholder more than once?
For portable, predictable PDO code, use a separate uniquely named placeholder for each value occurrence, even if the value is the same.
Mini Project
Description
Build a small batch inventory updater. It reuses one prepared UPDATE statement to apply multiple stock changes, demonstrating why binding variables by reference with bindParam() is useful in repeated database operations.
Goal
Update the quantity of several products by changing bound variables and executing the same prepared statement for each product.
Requirements
Use a PDO connection named $pdo. Prepare one SQL UPDATE statement with named placeholders. Bind the SKU and quantity variables with bindParam(). Loop through at least three inventory updates. Execute the statement once per update.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.