Package · cdo

Parameter binding

Every value CDO writes goes through CDOStatement::bindTypedValue(), which picks a PDO::PARAM_* constant from the value’s PHP type. This is what lets you pass a bool, an int, an array, or a DateTime and get correct SQL without casting — here’s exactly how it decides.

The type switch

bindTypedValue($parameter, $value) reads gettype($value) and dispatches:

PHP type PDO type Transform
null PARAM_NULL
bool PARAM_BOOL
int PARAM_INT
array PARAM_STR json_encode($value) first
object PARAM_STR serialised via valObject() (below)
float, string, other PARAM_STR bound as-is

This runs for insert/upsert column values, update SET values, and every Qb bind — a single, consistent path.

Why booleans and ints matter

Binding a bool as PARAM_BOOL and an int as PARAM_INT (rather than letting everything default to string) keeps PostgreSQL happy with boolean columns and lets numeric comparisons use indexes correctly. It’s the difference between WHERE active = true and the driver choking on '1'.

Arrays become JSON

An array value is json_encoded before binding, so a JSON/JSONB column gets valid JSON with no extra work:

php
$cdo->update('products',
  ['attributes' => ['color' => 'red', 'size' => 'M']],
  Qb::eq('id', 42)
);
// attributes bound as '{"color":"red","size":"M"}'

Object serialisation (valObject)

When a value is an object, valObject() converts it to a scalar by checking interfaces in this order — first match wins:

Priority Interface / type Conversion
1 JsonSerializable $value->jsonSerialize()
2 Stringable (string) $value
3 DateTimeInterface $value->format('Y-m-d H:i:s')
4 BackedEnum $value->value
5 anything else serialize($value) (PHP serialisation)
php
$cdo->insert('events', [
  'occurred_at' => new DateTimeImmutable('2026-07-01 12:00:00'), // → '2026-07-01 12:00:00'
  'status'      => Status::Active,   // BackedEnum → its ->value
  'payload'     => $dtoImplementingJsonSerializable, // → jsonSerialize()
]);

Ordering has consequences

Because JsonSerializable is checked first, an object that is both JsonSerializable and DateTimeInterface is JSON-encoded, not formatted as a datetime. And the priority-5 fallback is PHP serialize() — rarely what a column wants. Implement Stringable or JsonSerializable on your value objects so they land on an intentional branch, not the serialize fallback.

Explicit binding and replay

CDOStatement also exposes lower-level control:

  • bindValue($param, $value, $type = PDO::PARAM_STR) — bind with an explicit PDO type, bypassing detection. Every bind (typed or explicit) is recorded.
  • getBindings() — the recorded [param, value, type] triples.
  • updateStm(PDOStatement $stmt) — swap in a freshly prepared statement and replay every recorded bind onto it. This is what makes a statement survivable across a reconnect: the values are re-applied verbatim to the new handle.

Reading it back

Binding is a write-path concern. When you read with inherited PDO methods, values come back as the driver returns them — a JSON column is a string you json_decode yourself, a datetime is a string. CDO does not reverse valObject() on read.