WordPress prepared statement with IN() condition - php

I have three values in a string like this:
$villes = '"paris","fes","rabat"';
When I feed it into a prepared statement like this:
$sql = 'SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN(%s)';
$query = $wpdb->prepare($sql, $villes);
echo $query; shows:
SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN('\"CHAPELLE VIVIERS \",\"LE MANS \",\"QUEND\"')
It is not writing the string as three separate values -- it is just one string with the double quotes escaped.
How can I properly implement a prepared statement in WordPress with multiple values?

Try this code:
// Create an array of the values to use in the list
$villes = array("paris", "fes", "rabat");
// Generate the SQL statement.
// The number of %s items is based on the length of the $villes array
$sql = "
SELECT DISTINCT telecopie
FROM `comptage_fax`
WHERE `ville` IN(".implode(', ', array_fill(0, count($villes), '%s')).")
";
// Call $wpdb->prepare passing the values of the array as separate arguments
$query = call_user_func_array(array($wpdb, 'prepare'), array_merge(array($sql), $villes));
echo $query;
implode()
array_fill()
call_user_func_array()
array_merge()

WordPress already has a function for this purpose, see esc_sql(). Here is the definition of this function:
Escapes data for use in a MySQL query. Usually you should prepare queries using wpdb::prepare(). Sometimes, spot-escaping is required or useful. One example is preparing an array for use in an IN clause.
You can use it like this:
$villes = ["paris", "fes", "rabat"];
$villes = array_map(function($v) {
return "'" . esc_sql($v) . "'";
}, $villes);
$villes = implode(',', $villes);
$query = "SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN (" . $villes . ")"

FUNCTION:
function escape_array($arr){
global $wpdb;
$escaped = array();
foreach($arr as $k => $v){
if(is_numeric($v))
$escaped[] = $wpdb->prepare('%d', $v);
else
$escaped[] = $wpdb->prepare('%s', $v);
}
return implode(',', $escaped);
}
USAGE:
$arr = array('foo', 'bar', 1, 2, 'foo"bar', "bar'foo");
$query = "SELECT values
FROM table
WHERE column NOT IN (" . escape_array($arr) . ")";
echo $query;
RESULT:
SELECT values
FROM table
WHERE column NOT IN ('foo','bar',1,2,'foo\"bar','bar\'foo')
May or may not be more efficient, however it is reusable.

Here is my approach for sanitizing IN (...) values for $wpdb.
I use a helper function that passes each value of the list through $wpdb->prepare() to ensure that it's properly escaped.
The prepared value-list is inserted into the SQL query via sprintf().
The helper function:
// Helper function that returns a fully sanitized value list.
function _prepare_in ( $values ) {
return implode( ',', array_map( function ( $value ) {
global $wpdb;
// Use the official prepare() function to sanitize the value.
return $wpdb->prepare( '%s', $value );
}, $values ) );
};
Sample usage:
// Sample 1 - note that we use "sprintf()" to build the SQL query!
$status_cond = sprintf(
'post_status IN (%s)',
_prepare_in( $status )
);
$posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE $status_cond;" );
// Sample 2:
$posts = $wpdb->get_col( sprintf( "
SELECT ID
FROM $wpdb->posts
WHERE post_status IN (%s) AND post_type IN (%s)
;",
_prepare_in( $status ),
_prepare_in( $post_types )
) );

I created a tiny function that will generate the placeholders for a prepared statement from an array, i.e., something like (%s,%d,%f), and conveniently, it will know exactly which placeholder to use depending on each item of the array.
function generate_wpdb_prepare_placeholders_from_array( $array ) {
$placeholders = array_map( function ( $item ) {
return is_string( $item ) ? '%s' : ( is_float( $item ) ? '%f' : ( is_int( $item ) ? '%d' : '' ) );
}, $array );
return '(' . join( ',', $placeholders ) . ')';
}
Considering the example from the question, first you'd need to convert the values to an array:
$villes = array( "paris", "fes", "rabat" );
$in_str = generate_wpdb_prepare_placeholders_from_array( $villes );
$sql = "SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN {$in_str}";
$query = $wpdb->prepare( $sql, $villes );

First is a modern set of a non-WordPress techniques using a mysqli prepared statement with an unknown number of values in an array. The second snippet will be the WordPress equivalent.
Let's assume that the indexed array of input data is untrusted and accessible from $_GET['villes']. A prepared statement is the modern standard and preferred by professional developers over old/untrusted escaping techniques. The snippet to follow will return rows that have one of the ville values specified in the array. If the array is not declared or is empty, it will return ALL rows in the database table.
Native PHP techniques:
$sql = "SELECT DISTINCT telecopie FROM comptage_fax";
if (!empty($_GET['villes'])) {
$count = count($_GET['villes']);
$commaDelimitedPlaceholders = implode(',', array_fill(0, $count, '?'));
$stmt = $conn->prepare("$sql WHERE ville IN ($commaDelimitedPlaceholders)");
$stmt->bind_param(str_repeat('s', $count), ...$_GET['villes']);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $conn->query($sql);
}
From this point, you can access the rows of distinct telecopie values (which is technically an iterable result set object) as if iterating an indexed array of associative arrays with a simple foreach().
foreach ($result as $row) {
echo $row['telecopie'];
}
With WordPress's helper methods the syntax is simpler because the variable binding and query execution is handled by get_results():
$sql = "SELECT DISTINCT telecopie FROM comptage_fax";
if (!empty($_GET['ville']) {
$commaDelimitedPlaceholders = implode(',', array_fill(0, count($_GET['ville']), '%s'));
$sql = $wpdb->prepare("$sql WHERE ville IN ($commaDelimitedPlaceholders)", $_GET['ville']);
}
$result = $wpdb->get_results($sql, ARRAY_A);
From this point, $result is an indexed array of associative arrays -- specifically because of ARRAY_A. $result is not a result set object like in the first native php snippet. This means that you can use both classic looping language constructor or the full suite of array_ functions on the data.
Useful References:
https://developer.wordpress.org/reference/classes/wpdb/prepare/
https://developer.wordpress.org/reference/classes/wpdb/get_results/

The prepare function also takes an array as the second parameter.
You can try converting $villes like this:
Current
<?php
$villes = '"paris","fes","rabat"';
?
Change it to
<?php
$villes = array("paris","fes","rabat");
?>
Now, try passing $villes to the prepare func and see if it works. Hope it helps.

Related

PDO: Delete query only deleting entries of first value in IN statement [duplicate]

I found this code on SO, which is great for using PDO and the IN() statement together.
$values = explode(',', $values) ; # 1,4,7
$placeholders = rtrim(str_repeat('?, ', count($values)), ', ') ;
$query = "SELECT * FROM table WHERE id IN ($placeholders)";
$stm = $db->prepare($query) ;
$stm->execute($values) ;
However, how can I mix in another addition to the query so the query looks like this:
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
$stm = $db->prepare($query) ;
$stm->execute(array($values,$product)) ; //error happens when adding product placeholder
I thought this would work but I get:
Warning: PDOStatement::execute() [pdostatement.execute]: SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in line 3 (the $stm line)
Any idea how to get this to behave as intended?
UPDATED execute to array, still not working..
Solution
This should work, if $values is an array:
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
$stm->execute(array_merge($values, array($product)));
Explanation
execute() expects one parameter - in this case an array - to be provided. By adding array_merge($values, array($product)) you create one array with $product added at the end, so the query should work correctly.
See the demo here: http://ideone.com/RcClX
$stm->execute($values,$product) ; //error happens when adding product placeholder
The problem here is that execute needs a single array. You can't pass multiple arrays, and worse, you can't nest arrays.
We already have a perfectly good $values array, so let's reuse it after you create the placeholder string.
$values = explode(',', $values) ; # 1,4,7
$placeholders = rtrim(str_repeat('?, ', count($values)), ', ') ;
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
// New!
$values[] = $product;
$stm = $db->prepare($query);
$stm->execute($values);
And an other solution can be (if you like the :param_name = $value way, as me):
$params = array(
':product' => $product
);
$_in_params = array();
foreach ( $_in_values as $idx_in => $value_in)
{
$_in_params[] = ':param_in_'.$idx_in;
$params[':param_in_'.$idx_in] = $value_in;
}
$query .= "SELECT * FROM table WHERE id IN (".join(',',$_in_params).") AND product=:product";
I'm not sure if this is the best and the most optimal solution, but it's a little bit more human readable :) And it can be helpful if you have a big an complicated query and you want to debug it
(I'm curious if someone have a good argument why NOT to do in this way)
You forgot to prepare it ^_^
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
$stm = $db->prepare($query) ;
$stm->execute($values,$product) ; //p00f
And aside from that execute() should only have one parameter
So the above won't work AT ALL!
See the DOCs
Placeholders version if you need it
$values = [1, 4, 7, 8];
$placeholders = preg_filter('/^/', ':prefix_', array_keys($values)));
$query = 'SELECT * FROM table WHERE id IN ( '. implode(', ', $placeholders) . ')';
$stmt = $db->prepare($query);
if (count($values) > 0) {
foreach ($values as $key => $current_value) {
$stmt->bindValue($placeholders[$key] , $current_value, PDO::PARAM_STR);
}
}
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

How to pass array values into prepared SQL WHERE IN statement? [duplicate]

I have three values in a string like this:
$villes = '"paris","fes","rabat"';
When I feed it into a prepared statement like this:
$sql = 'SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN(%s)';
$query = $wpdb->prepare($sql, $villes);
echo $query; shows:
SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN('\"CHAPELLE VIVIERS \",\"LE MANS \",\"QUEND\"')
It is not writing the string as three separate values -- it is just one string with the double quotes escaped.
How can I properly implement a prepared statement in WordPress with multiple values?
Try this code:
// Create an array of the values to use in the list
$villes = array("paris", "fes", "rabat");
// Generate the SQL statement.
// The number of %s items is based on the length of the $villes array
$sql = "
SELECT DISTINCT telecopie
FROM `comptage_fax`
WHERE `ville` IN(".implode(', ', array_fill(0, count($villes), '%s')).")
";
// Call $wpdb->prepare passing the values of the array as separate arguments
$query = call_user_func_array(array($wpdb, 'prepare'), array_merge(array($sql), $villes));
echo $query;
implode()
array_fill()
call_user_func_array()
array_merge()
WordPress already has a function for this purpose, see esc_sql(). Here is the definition of this function:
Escapes data for use in a MySQL query. Usually you should prepare queries using wpdb::prepare(). Sometimes, spot-escaping is required or useful. One example is preparing an array for use in an IN clause.
You can use it like this:
$villes = ["paris", "fes", "rabat"];
$villes = array_map(function($v) {
return "'" . esc_sql($v) . "'";
}, $villes);
$villes = implode(',', $villes);
$query = "SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN (" . $villes . ")"
FUNCTION:
function escape_array($arr){
global $wpdb;
$escaped = array();
foreach($arr as $k => $v){
if(is_numeric($v))
$escaped[] = $wpdb->prepare('%d', $v);
else
$escaped[] = $wpdb->prepare('%s', $v);
}
return implode(',', $escaped);
}
USAGE:
$arr = array('foo', 'bar', 1, 2, 'foo"bar', "bar'foo");
$query = "SELECT values
FROM table
WHERE column NOT IN (" . escape_array($arr) . ")";
echo $query;
RESULT:
SELECT values
FROM table
WHERE column NOT IN ('foo','bar',1,2,'foo\"bar','bar\'foo')
May or may not be more efficient, however it is reusable.
Here is my approach for sanitizing IN (...) values for $wpdb.
I use a helper function that passes each value of the list through $wpdb->prepare() to ensure that it's properly escaped.
The prepared value-list is inserted into the SQL query via sprintf().
The helper function:
// Helper function that returns a fully sanitized value list.
function _prepare_in ( $values ) {
return implode( ',', array_map( function ( $value ) {
global $wpdb;
// Use the official prepare() function to sanitize the value.
return $wpdb->prepare( '%s', $value );
}, $values ) );
};
Sample usage:
// Sample 1 - note that we use "sprintf()" to build the SQL query!
$status_cond = sprintf(
'post_status IN (%s)',
_prepare_in( $status )
);
$posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE $status_cond;" );
// Sample 2:
$posts = $wpdb->get_col( sprintf( "
SELECT ID
FROM $wpdb->posts
WHERE post_status IN (%s) AND post_type IN (%s)
;",
_prepare_in( $status ),
_prepare_in( $post_types )
) );
I created a tiny function that will generate the placeholders for a prepared statement from an array, i.e., something like (%s,%d,%f), and conveniently, it will know exactly which placeholder to use depending on each item of the array.
function generate_wpdb_prepare_placeholders_from_array( $array ) {
$placeholders = array_map( function ( $item ) {
return is_string( $item ) ? '%s' : ( is_float( $item ) ? '%f' : ( is_int( $item ) ? '%d' : '' ) );
}, $array );
return '(' . join( ',', $placeholders ) . ')';
}
Considering the example from the question, first you'd need to convert the values to an array:
$villes = array( "paris", "fes", "rabat" );
$in_str = generate_wpdb_prepare_placeholders_from_array( $villes );
$sql = "SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN {$in_str}";
$query = $wpdb->prepare( $sql, $villes );
First is a modern set of a non-WordPress techniques using a mysqli prepared statement with an unknown number of values in an array. The second snippet will be the WordPress equivalent.
Let's assume that the indexed array of input data is untrusted and accessible from $_GET['villes']. A prepared statement is the modern standard and preferred by professional developers over old/untrusted escaping techniques. The snippet to follow will return rows that have one of the ville values specified in the array. If the array is not declared or is empty, it will return ALL rows in the database table.
Native PHP techniques:
$sql = "SELECT DISTINCT telecopie FROM comptage_fax";
if (!empty($_GET['villes'])) {
$count = count($_GET['villes']);
$commaDelimitedPlaceholders = implode(',', array_fill(0, $count, '?'));
$stmt = $conn->prepare("$sql WHERE ville IN ($commaDelimitedPlaceholders)");
$stmt->bind_param(str_repeat('s', $count), ...$_GET['villes']);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $conn->query($sql);
}
From this point, you can access the rows of distinct telecopie values (which is technically an iterable result set object) as if iterating an indexed array of associative arrays with a simple foreach().
foreach ($result as $row) {
echo $row['telecopie'];
}
With WordPress's helper methods the syntax is simpler because the variable binding and query execution is handled by get_results():
$sql = "SELECT DISTINCT telecopie FROM comptage_fax";
if (!empty($_GET['ville']) {
$commaDelimitedPlaceholders = implode(',', array_fill(0, count($_GET['ville']), '%s'));
$sql = $wpdb->prepare("$sql WHERE ville IN ($commaDelimitedPlaceholders)", $_GET['ville']);
}
$result = $wpdb->get_results($sql, ARRAY_A);
From this point, $result is an indexed array of associative arrays -- specifically because of ARRAY_A. $result is not a result set object like in the first native php snippet. This means that you can use both classic looping language constructor or the full suite of array_ functions on the data.
Useful References:
https://developer.wordpress.org/reference/classes/wpdb/prepare/
https://developer.wordpress.org/reference/classes/wpdb/get_results/
The prepare function also takes an array as the second parameter.
You can try converting $villes like this:
Current
<?php
$villes = '"paris","fes","rabat"';
?
Change it to
<?php
$villes = array("paris","fes","rabat");
?>
Now, try passing $villes to the prepare func and see if it works. Hope it helps.

Mysql query parameter binding issue [duplicate]

I have three values in a string like this:
$villes = '"paris","fes","rabat"';
When I feed it into a prepared statement like this:
$sql = 'SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN(%s)';
$query = $wpdb->prepare($sql, $villes);
echo $query; shows:
SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN('\"CHAPELLE VIVIERS \",\"LE MANS \",\"QUEND\"')
It is not writing the string as three separate values -- it is just one string with the double quotes escaped.
How can I properly implement a prepared statement in WordPress with multiple values?
Try this code:
// Create an array of the values to use in the list
$villes = array("paris", "fes", "rabat");
// Generate the SQL statement.
// The number of %s items is based on the length of the $villes array
$sql = "
SELECT DISTINCT telecopie
FROM `comptage_fax`
WHERE `ville` IN(".implode(', ', array_fill(0, count($villes), '%s')).")
";
// Call $wpdb->prepare passing the values of the array as separate arguments
$query = call_user_func_array(array($wpdb, 'prepare'), array_merge(array($sql), $villes));
echo $query;
implode()
array_fill()
call_user_func_array()
array_merge()
WordPress already has a function for this purpose, see esc_sql(). Here is the definition of this function:
Escapes data for use in a MySQL query. Usually you should prepare queries using wpdb::prepare(). Sometimes, spot-escaping is required or useful. One example is preparing an array for use in an IN clause.
You can use it like this:
$villes = ["paris", "fes", "rabat"];
$villes = array_map(function($v) {
return "'" . esc_sql($v) . "'";
}, $villes);
$villes = implode(',', $villes);
$query = "SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN (" . $villes . ")"
FUNCTION:
function escape_array($arr){
global $wpdb;
$escaped = array();
foreach($arr as $k => $v){
if(is_numeric($v))
$escaped[] = $wpdb->prepare('%d', $v);
else
$escaped[] = $wpdb->prepare('%s', $v);
}
return implode(',', $escaped);
}
USAGE:
$arr = array('foo', 'bar', 1, 2, 'foo"bar', "bar'foo");
$query = "SELECT values
FROM table
WHERE column NOT IN (" . escape_array($arr) . ")";
echo $query;
RESULT:
SELECT values
FROM table
WHERE column NOT IN ('foo','bar',1,2,'foo\"bar','bar\'foo')
May or may not be more efficient, however it is reusable.
Here is my approach for sanitizing IN (...) values for $wpdb.
I use a helper function that passes each value of the list through $wpdb->prepare() to ensure that it's properly escaped.
The prepared value-list is inserted into the SQL query via sprintf().
The helper function:
// Helper function that returns a fully sanitized value list.
function _prepare_in ( $values ) {
return implode( ',', array_map( function ( $value ) {
global $wpdb;
// Use the official prepare() function to sanitize the value.
return $wpdb->prepare( '%s', $value );
}, $values ) );
};
Sample usage:
// Sample 1 - note that we use "sprintf()" to build the SQL query!
$status_cond = sprintf(
'post_status IN (%s)',
_prepare_in( $status )
);
$posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE $status_cond;" );
// Sample 2:
$posts = $wpdb->get_col( sprintf( "
SELECT ID
FROM $wpdb->posts
WHERE post_status IN (%s) AND post_type IN (%s)
;",
_prepare_in( $status ),
_prepare_in( $post_types )
) );
I created a tiny function that will generate the placeholders for a prepared statement from an array, i.e., something like (%s,%d,%f), and conveniently, it will know exactly which placeholder to use depending on each item of the array.
function generate_wpdb_prepare_placeholders_from_array( $array ) {
$placeholders = array_map( function ( $item ) {
return is_string( $item ) ? '%s' : ( is_float( $item ) ? '%f' : ( is_int( $item ) ? '%d' : '' ) );
}, $array );
return '(' . join( ',', $placeholders ) . ')';
}
Considering the example from the question, first you'd need to convert the values to an array:
$villes = array( "paris", "fes", "rabat" );
$in_str = generate_wpdb_prepare_placeholders_from_array( $villes );
$sql = "SELECT distinct telecopie FROM `comptage_fax` WHERE `ville` IN {$in_str}";
$query = $wpdb->prepare( $sql, $villes );
First is a modern set of a non-WordPress techniques using a mysqli prepared statement with an unknown number of values in an array. The second snippet will be the WordPress equivalent.
Let's assume that the indexed array of input data is untrusted and accessible from $_GET['villes']. A prepared statement is the modern standard and preferred by professional developers over old/untrusted escaping techniques. The snippet to follow will return rows that have one of the ville values specified in the array. If the array is not declared or is empty, it will return ALL rows in the database table.
Native PHP techniques:
$sql = "SELECT DISTINCT telecopie FROM comptage_fax";
if (!empty($_GET['villes'])) {
$count = count($_GET['villes']);
$commaDelimitedPlaceholders = implode(',', array_fill(0, $count, '?'));
$stmt = $conn->prepare("$sql WHERE ville IN ($commaDelimitedPlaceholders)");
$stmt->bind_param(str_repeat('s', $count), ...$_GET['villes']);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $conn->query($sql);
}
From this point, you can access the rows of distinct telecopie values (which is technically an iterable result set object) as if iterating an indexed array of associative arrays with a simple foreach().
foreach ($result as $row) {
echo $row['telecopie'];
}
With WordPress's helper methods the syntax is simpler because the variable binding and query execution is handled by get_results():
$sql = "SELECT DISTINCT telecopie FROM comptage_fax";
if (!empty($_GET['ville']) {
$commaDelimitedPlaceholders = implode(',', array_fill(0, count($_GET['ville']), '%s'));
$sql = $wpdb->prepare("$sql WHERE ville IN ($commaDelimitedPlaceholders)", $_GET['ville']);
}
$result = $wpdb->get_results($sql, ARRAY_A);
From this point, $result is an indexed array of associative arrays -- specifically because of ARRAY_A. $result is not a result set object like in the first native php snippet. This means that you can use both classic looping language constructor or the full suite of array_ functions on the data.
Useful References:
https://developer.wordpress.org/reference/classes/wpdb/prepare/
https://developer.wordpress.org/reference/classes/wpdb/get_results/
The prepare function also takes an array as the second parameter.
You can try converting $villes like this:
Current
<?php
$villes = '"paris","fes","rabat"';
?
Change it to
<?php
$villes = array("paris","fes","rabat");
?>
Now, try passing $villes to the prepare func and see if it works. Hope it helps.

PHP security concerns around in clause that is a concatenated string

Given the following code
<?php
$values = array('Foo' =>'Foo' ,'Bar' =>'Bar' );
$separated = "'" . implode("','", $values)."'";
$sql = 'SELECT NAME,AGE FROM CATS WHERE TITLE IN(' .$separated.')' ;
print_r($sql);
produces:
SELECT NAME,AGE FROM CATS WHERE TITLE IN('Foo','Bar')
Is there anything I need to be aware of about SQL injection using this type of query builder? If so, what is an attack that can occur?
The only rule of SQL security:
NO value should be added to a query directly, but via placeholder only
So, you have to use a library that supports placeholders.
Assuming your database is mysql, the best choice would be safemysql, which will let you have as simple code as this:
$sql = 'SELECT NAME,AGE FROM CATS WHERE TITLE IN(?a)';
$data = $db->getArr($sql, $values);
print_r($data);
or you can use PDO, but it will take you a lot more trouble
You should never use any variables in queries no matter where they come from. A solution for PDO and parameterized queries will be to add placeholders to the query.
I do it something like this:
function getPlaceholders ($array) {
return !empty($array)
? implode(',', array_fill(0, count($array), '?'))
: null;
}
$userIds = array(1,2,3,4);
$sql = 'SELECT FROM users WHERE id IN (' . $this->getPlaceholders($userIds) . ')';
$result = pdo_query($sql, $userIds);
Normally you would have this in a OOP-format.
$userIds = array(1,2,3,4);
$sql = 'SELECT FROM users WHERE id IN (' . $this->getPlaceholders($userIds) . ')';
$result = $this->db->query($sql, $userIds);
// common file which is extended
public function getPlaceholders ($array) {
return !empty($array)
? implode(',', array_fill(0, count($array), '?'))
: null;
}
This will generate a query like:
SELECT FROM users WHERE id IN (?,?,?,?)

PDO PHP bindValue doesn't work

I know this has been asked 1000 times, but for some reason I continue to bang my head agains the wall..
This works:
$sql = 'SELECT a.eventCode, a.eventTime, a.teamCode, a.playerCode, b.lastName, b.firstName, b.number, a.xCoord, a.yCoord, a.id ';
$sql = $sql . 'FROM events a, players b ';
$sql = $sql . 'WHERE a.regGUID in ( ' . $regGUID . ' ) and ';
$sql = $sql . 'a.playerCode=b.playerCode and a.gameCode = "' . $game . '" order by a.eventTime desc, a.actionCode asc';
$stmt = $db->prepare($sql);
$results = $stmt->execute();
This Doesn't:
$sql = 'SELECT a.eventCode, a.eventTime, a.teamCode, a.playerCode, b.lastName, b.firstName, b.number, a.xCoord, a.yCoord, a.id ';
$sql = $sql . 'FROM events a, players b ';
$sql = $sql . 'WHERE a.regGUID in ( :regGUID ) and ';
$sql = $sql . 'a.playerCode=b.playerCode and a.gameCode = :game order by a.eventTime desc, a.actionCode asc';
$stmt = $db->prepare($sql);
$stmt->bindValue(':regGUID', $regGUID, PDO::PARAM_STR);
$stmt->bindValue(':game', $game, PDO::PARAM_STR);
$results = $stmt->execute();
What am I missing? Thanks
The problem is here:
$sql = $sql . 'WHERE a.regGUID in ( :regGUID ) and ';
$stmt->bindValue(':regGUID', $regGUID, PDO::PARAM_STR);
I assume $regGUID is a comma-separated list of quoted strings.
Each query parameter accepts only a single scalar value. Not lists of values.
So you have two choices:
Continue to interpolate the $regGUID string, even if you use parameters for other scalar values. But you still want to be careful to avoid SQL injection, so you must form the $regGUID string correctly. You can't just call PDO::quote() on the whole string, that would make it a single quoted string containing UUIDs and commas. You have to make sure each UUID string is escaped and quoted individually, then implode the list together and interpolate it into the IN clause.
$regGUIDs = explode(',', $regGUID);
$regGUIDs = array_map(function ($g) { return $db->quote($g); }, $regGUIDs);
$regGUID = implode(',', $regGUIDs);
$sql = $sql . 'WHERE a.regGUID in (' . $regGUID . ') and ';
explode() the $regGUID into an array, and add one query parameter for each element in the array. Interpolate the dynamic list of query parameter placeholders.
$regGUIDs = explode(',', $regGUID);
$params = array_fill(1, count($regGUIDs), '?');
$sql = $sql . ' WHERE a.regGUID in ( ' . implode(',', $params) . ' ) and ';
You could bindValue() in a loop for the array, but keep in mind that other parameters should also be bound by position, not by name. PDO has bugs that make it not happy when you try to mix the two different styles of parameters in the same query.
Instead of using bindValue() I just pass an array of parameter values to PDOStatement::execute(), which is much easier.
$paramValues = $regGUIDs;
$paramValues[] = $game;
$results = $stmt->execute($paramValues);
This indeed has been asked 1000 times.
Prepared statements can only accept scalar values, not arbitrary parts of the SQL query.
You have to form IN() statement using as many placeholders, as many items you have to put in and then bind them one by one.
To ease this task one can use some helper function.
Say, using SafeMysql library this code could be written as
$sql = 'SELECT * FROM events a, players b WHERE regGUID in (?a) and';
$sql .= ' a.playerCode=b.playerCode and a.gameCode = ?s';
$sql .= ' order by a.eventTime desc, a.actionCode asc';
$results = $db->getAll($sql,$regGUID,$game);
Note that $regGUID should be an array, not string and $results already contain all the requested data, without any further processing.
What is the content of $regGUID? Since you're using an in clause, I suspect a comma separated list.
Binding a variable to a parameter is not like substituting that string into the query; it is like telling MySQL how to use your actual PHP variables. So if you bind a string like '1,2,3' to the query parameter, it stays as one string, and is not reinterpreted as a list of numbers.
Consequently, if $regGUID is something like "'AAA1', 'BBB2'", your first query becomes
... WHERE a.regGUID in ( 'AAA1', 'BBB2' ) ...
but your second query is more like
... WHERE a.regGUID in ( '\'AAA1\', \'BBB2\'' ) ...
which is the same as saying
... WHERE a.regGUID = '\'AAA1\', \'BBB2\'' ...
As others have state you can only bind a single scalar value to a placeholder. So this means you actually need a placeholder for each value in your IN statement. I normally do something like the following. It should be noted though that i never use bindValue so if it has rules about things having to be references like Mysqli then the below may need to be modified:
$regGUIDPlaceholders = array();
// prepare the placeholders
// assume regGUID is an array - if its a string then explode on whatever to make it an array
foreach($regGUID as $k => $v) {
$placeholder = ':regGUID' . $k;
$regGUIDPlaceholders[$key] = $value;
}
// prepare the IN statememnt
$in = sprintf('IN (%s)', implode(',', array_keys($regGUIDPlaceholders)));
$sql = 'SELECT a.eventCode, a.eventTime, a.teamCode, a.playerCode, b.lastName, b.firstName, b.number, a.xCoord, a.yCoord, a.id ';
$sql = $sql . 'FROM events a, players b ';
// USE the IN statement dynamically prepared above
$sql = $sql . 'WHERE a.regGUID '. $in . ' and ';
$sql = $sql . 'a.playerCode=b.playerCode and a.gameCode = :game order by a.eventTime desc, a.actionCode asc';
$stmt = $db->prepare($sql);
// bind each GUID to its placeholder
foreach($regGUIDPlaceholders as $placeholder => $value) {
$stmt->bindValue($placeholder, $value, PDO::PARAM_STR);
}
$stmt->bindValue(':game', $game, PDO::PARAM_STR);
$results = $stmt->execute();

Categories