Question

PDO bindParam vs bindValue in PHP: Key Differences

phppdobindparambindvalue

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 when execute() runs.
  • bindValue() binds the value immediately when you call bindValue().

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 with execute(), 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.

Take Quiz

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:

  1. prepare() creates a statement with a named placeholder, :id.
  2. $productId is initially 10.
  3. bindParam() connects :id to the variable $productId by reference. It does not permanently store 10 at this point.
  4. $productId changes to 25.
  5. execute() reads the current referenced value, so the query receives 25.

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 as GET /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 one INSERT statement and change bound variables for every CSV row.
  • Data migrations: Reuse a prepared UPDATE statement while iterating through many records and assigning new values before each execution.
  • Typed API filters: Use PDO::PARAM_INT, PDO::PARAM_BOOL, or PDO::PARAM_NULL to make the intended SQL value type clear.
Take Quiz

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

FeaturebindParam()bindValue()
What it bindsA variable by referenceA value at the time of the call
Value used by execute()The variable's current valueThe value already stored
Accepts literals and expressionsNoYes
Best forRepeated execution with changed variablesOne-off queries and final values
Can specify a PDO typeYesYes

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.

Take Quiz

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 before execute() to use its new value.
  • Rebind with bindValue() if the desired value changes.
  • Use PDO::PARAM_INT for integers, PDO::PARAM_BOOL for booleans, PDO::PARAM_NULL for null, and PDO::PARAM_STR for 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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more