I was told that "There is no way to bind an array to an SQL statement using prepared statements" but I have done it. I am having trouble recreating it though.
I have a statement that updates the database:
if (isset($_POST['printRow'])){
$ids = "";
foreach ($_POST['checkbox'] as $rowid)
{
if(!empty($ids)) $ids .= ',';
$ids .= $rowid;
$_SESSION['ids'] = $ids;
}
}
Here I forgot to post the WORKING code:
$stmt = $conn->prepare("UPDATE just_ink SET deleted=1 WHERE ID IN( " . $ids . ")");
$stmt->execute();
But I still have the following problem:
Where $ids can be either one or multiple ids.
So here is the problem, if I try to take $ids and set a SESSION with it
($_SESSION['ids'] = $ids;)
For use on another page.
On the next page I want to select data using $_SESSION['ids'] so,
$stmt = $conn->prepare("SELECT * FROM just_ink WHERE ID IN( " . $_SESSION['ids'] . ")");
$stmt->execute();
But this doesn't work. Any ideas why?
It doesn't work, because, as you correctly said, you can't bind an array to an SQL statement using prepared statements.
The correct way to bind an array is to create a string of placeholders (question marks) and then bind params in a loop.
Let's say you have an array of necessary ID's called $checkboxes. First, we need to create a string that we will use in our query to bind required params. If $checkboxes has 3 items, our string will look like
$placeholder = "?,?,?";
For this we can use str_repeat function to create a string, where every but last element will add ?, part to placeholder. For last element we need to concatenate single question mark.
$placeholder = str_repeat('?,', count($checkboxes)-1).'?';
Now we need to form and prepare a query that will contain our placeholders:
$query = 'SELECT * FROM just_ink WHERE ID IN (".$placeholder.")';
$stmt = $conn->prepare($query);
To bind every ID to its placeholder we use bindParam method in a loop:
for ($i=0; $i<count($checkboxes); $i++) {
$stmt->bindParam($i+1, ($checkboxes[$i]); #position is 1-indexed
}
$stmt->execute();
You can use arrays with mysqli prepared statements by using call_user_func_array
Your code would end up something like this
$varArray = array();
$questionArray = array();
foreach ($_POST['checkbox'] as $daNumber=>$daValue) {
$questionArray[] = "?";
//We're declaring these as strings, if they were ints, they would be i
$varArray[0] .= 's';
//These must be relational variables. The ampersand is vry important.
$varArray[] = &$_POST['checkbox'][$daNumber];
}
//comma separated series of questionmarks
$allDaQuestions = implode(', ', $questionArray);
$query = "SELECT * FROM just_ink WHERE ID IN ($allDaQuestions)";
$stmt = $conn->prepare($query);
//Where the magic happens
call_user_func_array(array(&$stmt, 'bind_param'), $varArray);
//continue with your regularly scheduled broadcast
$stmt->execute();
//etc.
did you set session_start() at the beginning of the file? you can't use $_SESSION if you don't do that first
Related
This question already has answers here:
PHP - Using PDO with IN clause array
(9 answers)
Closed 24 days ago.
I did have this code working but it was not secure from SQL injection so I tried to update it. The user submit filter requirements which come from check boxes, I take them from post, and replace them with ?, I then implode, and bind them back together for the IN clause.
I feel like I am a couple of lines of code away from getting this. What am I doing wrong? I have echoed out after implode and it shows the right amount of ? corresponding to the selections. Is the problem in my execute statement?
if(isset($_POST['songgenre'])){
$songgenre = $_POST['songgenre'];
$placeholderssonggenre = array_fill(1, count($songgenre), '?');
$songgenrefilter = implode(',', $placeholderssonggenre);
}else{
$songgenre ='';
$genreempty = '';
}
$sql = "SELECT * FROM music_db WHERE songgenre IN ($songgenrefilter)";
$stmt = $conn->prepare($sql);
$res = $stmt->execute($songgenre);
if ($res !== FALSE) {
$results = $res->rowCount();
echo($results);
} else {
echo "Code Failed";
}
I know really how frustrating it could be. Well, it seems that following things are missing there. Try it:
When you're using the IN clause, you need to pass an array of values to the execute() method, not a single variable. This means that you should be passing $songgenre directly to the execute() method, instead of using it to create the $placeholderssonggenre variable.
You should bind the parameters to the statement before executing it, not after. You can use the bindValue() method to bind the values to the placeholders in the query.
When you execute the statement, you should use the fetchAll() method to retrieve the rows, instead of rowCount().
Here's the correct code:
if(isset($_POST['songgenre'])){
$songgenre = $_POST['songgenre'];
$placeholders = array_fill(0, count($songgenre), '?');
$placeholders = implode(',', $placeholders);
} else {
$songgenre ='';
$placeholders = '';
}
$sql = "SELECT * FROM music_db WHERE songgenre IN ($placeholders)";
$stmt = $conn->prepare($sql);
foreach ($songgenre as $i => $value) {
$stmt->bindValue($i + 1, $value);
}
$stmt->execute();
$results = $stmt->fetchAll();
Another option more elegant
if(isset($_POST['songgenre'])){
$postfilter = [
'songgenre' => ['filter' => FILTER_SANITIZE_STRING] // always sanitize
];
$filter_post_array = filter_var_array($_POST, $postfilter);
$filtered = $filter_post_array['songgenre'];
} else {
$filtered = [];
}
$sql = "SELECT * FROM music_db WHERE songgenre IN (:songgenrefilter)";
$stmt = $conn->prepare($sql);
$stmt->bindValue(':songgenrefilter',implode(',',$filtered);
$stmt->execute();
$result = $stmt->fetchAll();
This question already has answers here:
How can I bind an array of strings with a mysqli prepared statement?
(7 answers)
Use an array in a mysqli prepared statement: `WHERE .. IN(..)` query [duplicate]
(8 answers)
Closed 2 years ago.
I have an array full of random content item ids. I need to run a mysql query (id in the array goes in the WHERE clause), using each ID that's in the array, in the order that they appear in the said array. How would I do this?
This will be an UPDATE query, for each individual ID in the array.
As with nearly all "How do I do SQL from within PHP" questions - You really should use prepared statements. It's not that hard:
$ids = array(2, 4, 6, 8);
// prepare an SQL statement with a single parameter placeholder
$sql = "UPDATE MyTable SET LastUpdated = GETDATE() WHERE id = ?";
$stmt = $mysqli->prepare($sql);
// bind a different value to the placeholder with each execution
for ($i = 0; $i < count($ids); $i++)
{
$stmt->bind_param("i", $ids[$i]);
$stmt->execute();
echo "Updated record ID: $id\n";
}
// done
$stmt->close();
Alternatively, you can do it like this:
$ids = array(2, 4, 6, 8);
// prepare an SQL statement with multiple parameter placeholders
$params = implode(",", array_fill(0, count($ids), "?"));
$sql = "UPDATE MyTable SET LastUpdated = GETDATE() WHERE id IN ($params)";
$stmt = $mysqli->prepare($sql);
// dynamic call of mysqli_stmt::bind_param hard-coded eqivalent
$types = str_repeat("i", count($ids)); // "iiii"
$args = array_merge(array($types), $ids); // ["iiii", 2, 4, 6, 8]
call_user_func_array(array($stmt, 'bind_param'), ref($args)); // $stmt->bind_param("iiii", 2, 4, 6, 8)
// execute the query for all input values in one step
$stmt->execute();
// done
$stmt->close();
echo "Updated record IDs: " . implode("," $ids) ."\n";
// ----------------------------------------------------------------------------------
// helper function to turn an array of values into an array of value references
// necessary because mysqli_stmt::bind_param needs value refereces for no good reason
function ref($arr) {
$refs = array();
foreach ($arr as $key => $val) $refs[$key] = &$arr[$key];
return $refs;
}
Add more parameter placeholders for other fields as you need them.
Which one to pick?
The first variant works with a variable number of records iteratively, hitting the database multiple times. This is most useful for UPDATE and INSERT operations.
The second variant works with a variable number of records too, but it hits the database only once. This is much more efficient than the iterative approach, obviously you can only do the same thing to all affected records. This is most useful for SELECT and DELETE operations, or when you want to UPDATE multiple records with the same data.
Why prepared statements?
Prepared statements are a lot safer because they make SQL injection attacks impossible. This is the primary reason to use prepared statements, even if it is more work to write them. A sensible habit to get into is: Always use prepared statements, even if you think it's "not really necessary." Neglect will come and bite you (or your customers).
Re-using the same prepared statement multiple times with different parameter values is more efficient than sending multiple full SQL strings to the database, because the database only needs to compile the statement once and can re-use it as well.
Only parameter values are sent to the database on execute(), so less data needs to go over the wire when used repeatedly.
In longer loops the execution time difference between using a prepared statement and sending plain SQL will become noticeable.
Using the "IN" Clause
Might be what you're after
$ids = array(2,4,6,8);
$ids = implode($ids);
$sql="SELECT * FROM my_table WHERE id IN($ids);";
mysql_query($sql);
otherwise, what's wrong with
$ids = array(2,4,6,8);
foreach($ids as $id) {
$sql="SELECT * FROM my_table WHERE ID = $id;";
mysql_query($sql);
}
Amen to Tomalak's comment on statements.
However, if you do not wish to use mysqli, you can always use intval() to prevent injection:
$ids = array(2, 4, 6, 8);
for ($i = 0; $i < count($ids); $i++)
{
mysql_query("UPDATE MyTable SET LastUpdated = GETDATE() WHERE id = " . intval($ids[$i]));
}
$values_filtered = array_filter('is_int', $values);
if (count($values_filtered) == count($values)) {
$sql = 'update table set attrib = 'something' where someid in (' . implode(',', $values_filtered) . ');';
//execute
} else {
//do something
}
You could do something like the following, however you need to be VERY careful that the array only contains integers otherwise you could end up with SQL injection.
You really don't want to be doing multiple queries to get the content out if you can help it. Something like this might be what you are after.
foreach ($array as $key = $var) {
if ((int) $var <= 0) {
unset($array[$key]);
}
}
$query = "SELECT *
from content
WHERE contentid IN ('".implode("','", $array)."')";
$result = mysql_query($query);
This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 8 years ago.
This is my code:
if(isset($_POST['abc']))
{
$things['abc'] = mysqli_real_escape_string($connect, implode("','", $_POST['abc']));
$result = mysqli_query($connect, "SELECT * FROM this_list WHERE abc_column IN ('{$things['abc']}')");
if (!$result)
{
echo "Error fetching results: " . mysqli_error();
}
else
{
while ($row = mysqli_fetch_array($result))
{
$abc[] = $row['description'];
}
}
}
The above code uses mysqli_real_escape_string(), and $things is an array with checkbox values that is received via POST. This array contains the list of strings separated by comma that I am using in the query.
When I was searching on the net, I noticed that some people say mysqli_real_escape_string() may prevent sql injection, I was thinking maybe prepared statement for checkbox values might be more safer against sql injection.
I have used prepared statement with separate parameters to prevent sql injection. But I am stuck on this one and I dont know how to change the above code to a prepare() statement since it uses an array $things['abc']. I tried searching and everytime I search array in prepared statement, I am getting info on Java, etc.. Can someone enlighten me on how I can do this with php please?
EDIT:
After the help from onetrickpony code below, this is what I have now:
if(isset($_POST['abc']))
{
$ph = rtrim(str_repeat('?,', count($_POST['abc'])), ',');
$query = sprintf("SELECT col1 FROM abc_table WHERE col2 IN (%s)", $ph);
$stmt = mysqli_prepare($connect, $query);
// bind variables
$params = array();
foreach($_POST['abc'] as $v)
$params[] = &$v;
array_unshift($params, $stmt, str_repeat('s', count($_POST['abc']))); // s = string type
call_user_func_array('mysqli_stmt_bind_param', $params);
mysqli_stmt_execute($stmt);
// Get the data result from the query.
mysqli_stmt_bind_result($stmt, $col1);
/* fetch values and store them to each variables */
while (mysqli_stmt_fetch($stmt)) {
$name[] = $col1;
echo $name;
}
//loop to echo and see whats stored in the array above
foreach($name as $v) {
echo $v;
}
// Close the prepared statement.
$stmt->close();
}
In the above code, the sqli method for prepare statement seems to work which is great. However, when I use the mysqli_stmt_bind_result(), the $name[] array inside the while loop only seems to print the last row.
UPDATE:
onetrickpony's code with the mysqli method for using php array in a Prepared Statement worked fine and it was a very good approach he had suggested. However, I have been having nightmare with the second half of the code which is trying to get the fetched array results to work. After trying for more than a day, I have given up on that and I have made the switch to PDO. Again onetrickpony's advise below was totally worth it. Making the switch to PDO made the code so much easier and simpler and couldnt believe it.
Try this:
// build placeholder string (?,?...)
$ph = rtrim(str_repeat('?,', count($_POST['abc'])), ',');
$query = sprintf("SELECT * FROM this_list WHERE abc_column IN (%s)", $ph);
$stm = mysqli_prepare($connect, $query);
// bind variables (see my notes below)
$params = array();
foreach($_POST['abc'] as $v)
$params[] = &$v;
// s = string type
array_unshift($params, $stm, str_repeat('s', count($_POST['abc'])));
call_user_func_array('mysqli_stmt_bind_param', $params);
mysqli_stmt_execute($stm);
It appears that mysqli_stmt_bind_param cannot be called multiple times to bind multiple variables. And even worse, it requires referenced variables. I'd recommend you switch to PDO, just because of these limitations that force you to write ugly code :)
As the title states: I am trying to update specific records in a MySQL data base using PDO and tokens to secure against any injection.
Here is my code:
Some arrays to help build the query:
$id = 1234
$values = array ('a','b','c',);
$variables = array ($A, $B, $C);
The query built via loop:
$sql = "UPDATE table1 SET ";
foreach($values as $value)
{
$sql .="$value = :$value, ";
}
$sql = rtrim($sql,', ');
$sql .=" WHERE id = '$id'";
Execution of query via PDO:
try
{
$pdo = new PDO('mysql:host=localhost; dbname=db01', $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare($sql);
foreach(array_combine($values, $variables) as $value=>$variable)
{
$stmt->bindParam(":$value", $variable);
}
$stmt->execute();
The result:
Every field in the specified record (matching $id) is set to the same value, which is always equal to the contents of the last variable listed in the array (in this example they would all contain the value held in $C)
echoing the SQL query shows it has been constructed correctly.
Any ideas? Thanks for your consideration
Extending from comment:
In your foreach loop, the $variable is a value, not a reference, so when you mysqli_stmt::execute(), you actually end up using the last $variable.
To avoid that, you'll have to use something like this:
$cache=array_combine($values,$variables);
foreach($cache as $value=>$variable)
{
$stmt->bindParam(":$value",$cache[$value]);
}
You have to make this way:
foreach(array_combine($values, $variables) as $value=>$variable)
{
$stmt->bindParam(":$value", $variable);
$stmt->execute();
}
Execute your query inside the for loop. Don't execute your query once the loop is done because it will only get the last value of your array. It will only execute once.
This question already has answers here:
How can I bind an array of strings with a mysqli prepared statement?
(7 answers)
Use an array in a mysqli prepared statement: `WHERE .. IN(..)` query [duplicate]
(8 answers)
Closed 2 years ago.
I have an array full of random content item ids. I need to run a mysql query (id in the array goes in the WHERE clause), using each ID that's in the array, in the order that they appear in the said array. How would I do this?
This will be an UPDATE query, for each individual ID in the array.
As with nearly all "How do I do SQL from within PHP" questions - You really should use prepared statements. It's not that hard:
$ids = array(2, 4, 6, 8);
// prepare an SQL statement with a single parameter placeholder
$sql = "UPDATE MyTable SET LastUpdated = GETDATE() WHERE id = ?";
$stmt = $mysqli->prepare($sql);
// bind a different value to the placeholder with each execution
for ($i = 0; $i < count($ids); $i++)
{
$stmt->bind_param("i", $ids[$i]);
$stmt->execute();
echo "Updated record ID: $id\n";
}
// done
$stmt->close();
Alternatively, you can do it like this:
$ids = array(2, 4, 6, 8);
// prepare an SQL statement with multiple parameter placeholders
$params = implode(",", array_fill(0, count($ids), "?"));
$sql = "UPDATE MyTable SET LastUpdated = GETDATE() WHERE id IN ($params)";
$stmt = $mysqli->prepare($sql);
// dynamic call of mysqli_stmt::bind_param hard-coded eqivalent
$types = str_repeat("i", count($ids)); // "iiii"
$args = array_merge(array($types), $ids); // ["iiii", 2, 4, 6, 8]
call_user_func_array(array($stmt, 'bind_param'), ref($args)); // $stmt->bind_param("iiii", 2, 4, 6, 8)
// execute the query for all input values in one step
$stmt->execute();
// done
$stmt->close();
echo "Updated record IDs: " . implode("," $ids) ."\n";
// ----------------------------------------------------------------------------------
// helper function to turn an array of values into an array of value references
// necessary because mysqli_stmt::bind_param needs value refereces for no good reason
function ref($arr) {
$refs = array();
foreach ($arr as $key => $val) $refs[$key] = &$arr[$key];
return $refs;
}
Add more parameter placeholders for other fields as you need them.
Which one to pick?
The first variant works with a variable number of records iteratively, hitting the database multiple times. This is most useful for UPDATE and INSERT operations.
The second variant works with a variable number of records too, but it hits the database only once. This is much more efficient than the iterative approach, obviously you can only do the same thing to all affected records. This is most useful for SELECT and DELETE operations, or when you want to UPDATE multiple records with the same data.
Why prepared statements?
Prepared statements are a lot safer because they make SQL injection attacks impossible. This is the primary reason to use prepared statements, even if it is more work to write them. A sensible habit to get into is: Always use prepared statements, even if you think it's "not really necessary." Neglect will come and bite you (or your customers).
Re-using the same prepared statement multiple times with different parameter values is more efficient than sending multiple full SQL strings to the database, because the database only needs to compile the statement once and can re-use it as well.
Only parameter values are sent to the database on execute(), so less data needs to go over the wire when used repeatedly.
In longer loops the execution time difference between using a prepared statement and sending plain SQL will become noticeable.
Using the "IN" Clause
Might be what you're after
$ids = array(2,4,6,8);
$ids = implode($ids);
$sql="SELECT * FROM my_table WHERE id IN($ids);";
mysql_query($sql);
otherwise, what's wrong with
$ids = array(2,4,6,8);
foreach($ids as $id) {
$sql="SELECT * FROM my_table WHERE ID = $id;";
mysql_query($sql);
}
Amen to Tomalak's comment on statements.
However, if you do not wish to use mysqli, you can always use intval() to prevent injection:
$ids = array(2, 4, 6, 8);
for ($i = 0; $i < count($ids); $i++)
{
mysql_query("UPDATE MyTable SET LastUpdated = GETDATE() WHERE id = " . intval($ids[$i]));
}
$values_filtered = array_filter('is_int', $values);
if (count($values_filtered) == count($values)) {
$sql = 'update table set attrib = 'something' where someid in (' . implode(',', $values_filtered) . ');';
//execute
} else {
//do something
}
You could do something like the following, however you need to be VERY careful that the array only contains integers otherwise you could end up with SQL injection.
You really don't want to be doing multiple queries to get the content out if you can help it. Something like this might be what you are after.
foreach ($array as $key = $var) {
if ((int) $var <= 0) {
unset($array[$key]);
}
}
$query = "SELECT *
from content
WHERE contentid IN ('".implode("','", $array)."')";
$result = mysql_query($query);