This is a generic variation of the regexp technique, for a numbered array of parameters.
It was a bit more paranoid than the accepted answer because quoting everything, numbers included, has bitten me in the backside more than once; in MySQL as well as elsewhere1, '123' is less than '13'. Same goes for 'NULL', which is not NULL, and 'false', which is obviously true.
It has now been pointed out to me that I was not paranoid enough :-), and my ? replacement technique ("#\\?#") was naïve, because the source query might contain question marks as text in the body for whatever reason:
$query = "SELECT CONCAT('Is ', @value, ' ', ?, '? ',
IF(@check != ? AND 123 > '13', 'Yes!', 'Uh, no?'))
;
$values = array('correct', false, 123);
// Expecting valid SQL, selecting 'correct' if check is not false for 123
// and answering Yes if @check is true.
Output:
SELECT CONCAT('Is ', @value, ' ', 'correct', '? ',
IF(check != false AND 123 > '13', 'Yes!', 'Uh, no?'))
;
Is THIS_TEST correct? Yes!
My simpler implementation would have thrown an exception seeing too many question marks. An even simpler implementation would have returned something like
Is THIS_TEST correcttrue Uh, no
So this is the amended function. NOTE: I know there are things regexes shouldn't do. I do not claim this function to be working in all instances and for all border cases. I claim it is a reasonable attempt. Feel free to comment or email with non-working test cases.
function boundQuery($db, $query, $values) {
$ret = preg_replace_callback(
"#(\\?)(?=(?:[^']|['][^']*')*$)#ms",
// Notice the &$values - here, we want to modify it.
function($match) use ($db, &$values) {
if (empty($values)) {
throw new PDOException('not enough values for query');
}
$value = array_shift($values);
// Handle special cases: do not quote numbers, booleans, or NULL.
if (is_null($value)) return 'NULL';
if (true === $value) return 'true';
if (false === $value) return 'false';
if (is_numeric($value)) return $value;
// Handle default case with $db charset
return $db->quote($value);
},
$query
);
if (!empty($values)) {
throw new PDOException('not enough placeholders for values');
}
return $ret;
}
One could also extend PDOStatement in order to supply a $stmt->boundString($values) method.
(1) since this is PHP, have you ever tried $a = 1...1; print $a;?