Skip to content

Parameter binding

Greg Bowler edited this page Aug 31, 2026 · 5 revisions

Parameter binding lets us keep SQL readable while still passing values from PHP safely and predictably.

Positional placeholders

Use ? when the query only needs a small number of values and the order is obvious.

select
	id,
	name,
	email
from
	user
where
	id = ?
limit 1
$user = $db->fetch("user/getById", 105);

Named placeholders

Use named placeholders when the meaning of each value is clearer than its position.

select
	id,
	name,
	email
from
	user
where
	email = :email
and
	isActive = :isActive
$user = $db->fetch("user/getByEmail", [
	"email" => "dev@example.com",
	"isActive" => true,
]);

Automatic conversions

The library prepares a few common value types for us:

  • bool values are converted to database-friendly truthy values.
  • DateTimeInterface values are formatted as Y-m-d H:i:s.
  • named arrays expand into indexed placeholders such as :ids__0, :ids__1, and so on.

Special bindings

Some placeholders are replaced directly in the SQL because the underlying database drivers do not treat them as ordinary values:

  • :groupBy
  • :orderBy
  • :limit
  • :offset
  • :infileName

Example:

select
	id,
	email
from
	user
order by :orderBy
limit :limit
offset :offset
$rows = $db->fetchAll("user/list", [
	"orderBy" => "id desc",
	"limit" => 20,
	"offset" => 40,
]);

orderBy and groupBy are intended for field names or field expressions. limit and offset must be integers.

Array expansion for IN (...)

If a named placeholder value is an array, the library expands it into individually bound parameters.

select
	id,
	customer
from
	purchase
where
	id in (:idList)
$rows = $db->fetchAll("purchase/listByIds", [
	"idList" => [1, 2, 3],
]);

Dynamic bindings

There are also reserved placeholders for query shapes that need to change structurally:

  • :__dynamicValueSet
  • :__dynamicIn
  • :__dynamicOr

__dynamicValueSet

This is useful for generating a multi-row values (...) section:

insert into audit(name, createdAt)
values (:__dynamicValueSet)
$db->insert("audit/insertMany", [
	"__dynamicValueSet" => [
		["name" => "A", "createdAt" => "2026-01-01 10:00:00"],
		["name" => "B", "createdAt" => "2026-01-01 10:05:00"],
	],
]);

__dynamicIn

This is useful when the final SQL needs a literal IN (...) list:

select id, customerId
from purchase
where id in (:__dynamicIn)
$rows = $db->fetchAll("purchase/findIn", [
	"__dynamicIn" => [1, 3, 4],
]);

__dynamicOr

This creates grouped or conditions from an array of key-value pairs:

select id, customerId, productId
from purchase
where :__dynamicOr
$rows = $db->fetchAll("purchase/findByPairs", [
	"__dynamicOr" => [
		["customerId" => "cust_2", "productId" => 101],
		["customerId" => "cust_3", "productId" => 103],
	],
]);

These placeholders are useful when we need the query structure to change, but still want the library to do that work consistently.


Next, move on to Raw SQL and result sets.

Clone this wiki locally