Trouble binding an imploded array into a mysql prepared statement - php

I am beating my head over the below syntax error. I am trying to bind an imploded array into a prepared statement, but I am getting the following syntax error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?' at line 1
Here is my code. Can anyone see where I am going wrong?
<?php
include('config.php');
$selected = $_POST['selected'];
if ($stmt = $mysqli->prepare("DELETE FROM email_addresses WHERE email_addresses IN ?")) {
$stmt->bind_param("s", "('" . implode("', '", $selected) . "')" );
$stmt->execute();
$stmt->close();
print "ok";
} else {
print $mysqli->error;
}
$mysqli->close();
?>
As a test, I tried:
print "('" . implode("', '", $selected) . "')";
Which correctly gives me
('me#me.com', 'you#you.com')

Let me save you some trouble and tell you what you're trying to do won't work anyway. You are only binding one parameter to your IN() function call. You think you're passing a comma separated list but you are actually only passing a comma separated string which is treated as one value. This means you will be search for one record with a value of "'me#me.com', 'you#you.com'" instead of records that match "me#me.com" or "you#you.com".
To overcome this you need to:
Dynamically generate your types string
Use call_user_func_array() to bind your parameters
You can generate the types string like this:
$types = str_repeat('s', count($selected));
All this does is create a string of s's that is as many characters as the number of elements in the array.
You would then bind your parameters using call_user_func_array() like this (notice I put the parenthesis back in for the IN() function):
if ($stmt = $mysqli->prepare("DELETE FROM email_addresses WHERE email_addresses IN (?)")) {
call_user_func_array(array($stmt, "bind_param"), array_merge($types, $selected));
But if you try this you will get an error about mysqli_stmt::bind_param() expecting parameter two to be passed by reference:
Warning: Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given
This is kind of annoying but easy enough to work around. To work around that you can use the following function:
function refValues($arr){
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
It just creates an array of values that are references to the values in the $selected array. This is enough to make mysqli_stmt::bind_param() happy:
if ($stmt = $mysqli->prepare("DELETE FROM email_addresses WHERE email_addresses IN (?)")) {
call_user_func_array(array($stmt, "bind_param"), array_merge($types, refValues($selected)));
Edit
As of PHP 5.6 you can now use the ... operator to make this even simpler:
$stmt->bind_param($types, ...$selected);

Related

Binding an array in MySQLi prepared Insert statement PHP [duplicate]

This question already has answers here:
How to bind mysqli bind_param arguments dynamically in PHP?
(8 answers)
Closed 2 years ago.
I tried multiple ways to create a function to bind dynamic array values into the MySQLi prepared statements. But I am getting error 'Uncaught mysqli_sql_exception: No data supplied for parameters in prepared statement'
Here is my code:
if (count($fields) == count($values)) {
$fielddata = implode(", ", $fields);
$questions = rtrim(str_repeat("?, ", count($values)), ", ");
$typedata = implode("", $type);
foreach ($values as $index => $current_val){ // build type string and parameters
$value .= '$values['.$index.'],';
}
$value = rtrim($value,',');
$statement = "INSERT INTO ".$table." (".$fielddata.") VALUES (".$questions.")";
$stmt = $db->prepare($statement);
$stmt->bind_param("sss", $value);
$stmt->execute();
$stmt->close();
echo "inserted";
}
The same code works when I replace
$stmt->bind_param("sss", $value);
with
$stmt->bind_param("sss",$values[0],$values[1],$values[2]);
bind_param() doesn't take a string that is a comma separated list of values, which seems to be what you are trying to pass it.
Move your foreach loop below prepare() and bind the values inside the loop.
if (count($fields) == count($values)) {
$fielddata = implode(", ", $fields);
$questions = rtrim(str_repeat("?, ", count($values)), ", ");
$typedata = implode("", $type);
//NOTE: You should verify that `$table` contains a valid table name.
$statement = "INSERT INTO {$table} ({$fielddata}) VALUES ({$questions})";
$stmt = $db->prepare($statement);
//bind parameters using variable unpacking (PHP 5.6+), assuming `$typedata` actually contains the proper types.
$stmt->bind_param($typedata, ...$values);
$stmt->execute();
$stmt->close();
echo "inserted";
}
You seem to be binding a single string as a second argument in your bind_param(). This method takes a number of variables by reference and binds them to the placeholders in the query and since you bound a single string the number of bound parameters does not match.
You need to store the values in an array and then unpack them using the splat operator.
if (count($fields) == count($values)) {
$fielddata = implode(", ", $fields);
$questions = rtrim(str_repeat("?, ", count($values)), ", ");
$statement = "INSERT INTO ".$table." (".$fielddata.") VALUES (".$questions.")";
$stmt = $db->prepare($statement);
$stmt->bind_param(str_repeat("s", count($values)), ...$values);
$stmt->execute();
}
Also, the type should be a list of letters denoting the type of each variable being bound. The best case is to bind them all as strings, so just repeat s for each bound variable.
Take care of SQL injection. You need to make sure that the field names are properly whitelisted. If these can be arbitrary values you could be vulnerable to SQL injection.

Mistake in SQL syntax.. (bindValue?)

I am trying to create an update query and I am looping in some set stuff to a var called $str and I cant seem to get it to work.
if (is_numeric($id)) {
if (!empty($values) && !empty($table_name)) {
$str = '';
$sql = "UPDATE `$table_name` SET :update_values WHERE `$column_name` = :id";
// Its one because we dont use ID like that
$i = 1;
foreach ($values as $key => $value) {
if ($key != $column_name) {
// Exclude the last one from having a comma at the end
if ($i == count($values) - 1) {
$str .= "$key='" . $value . "'";
} else {
$str .= "$key='" . $value . "', ";
$i++;
}
}
}
$query = $this->dbh->prepare($sql);
$query->bindValue('update_values', $str, PDO::PARAM_STR);
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute();
return true;
} else {
return false;
}
} else{
return false;
}
}
Output:
Fatal error: Uncaught PDOException: SQLSTATE[42000]: Syntax error or
access violation: 1064 You have an error in your SQL syntax; check the
manual that corresponds to your MariaDB server version for the right
syntax to use near ''note_name=\'yeet\', note_date=\'2020-02-20\',
note_desc=\'asdasdasdasdadsasdads' at line 1
Am I making any obvious mistakes?
Also for the life of me I don't know what the backslashes in front of the values mean.
In MySQL, identifiers cannot be provided as values.
References to columns must appear in the text of the SQL statement, they cannot be provided through bind parameters. This holds true for table names, column names, function names.
There is no workaround; this is a by-design restriction. There's several reasons for this. One of the most straightforward reasons is understanding how a SQL statement gets prepared, the information that is needed to come up with an execution plan, the tables and columns have to be known at prepare time (for the semantic check and privilege check. The actual values can be deferred to execution time.
Bind placeholders are for providing values, not identifiers.
With the code given, what MySQL is seeing something along the lines of
UPDATE `mytable` SET 'a string value' WHERE `id_col` = 42
And MySQL is balking at the 'a string value'.
We can (and should) use bind parameters for values.
We could dynamically generate SQL text that looks like this:
UPDATE `mytable`
SET `col_one` = :val1
, `col_two` = :val2
WHERE `id_col` = :id
and after the SQL text is prepared into statement, we can bind values:
$sth->bindValue(':val1', $value_one , PDO::PARAM_STR );
$sth->bindValue(':val2', $value_two , PDO::PARAM_STR );
$sth->bindValue(':id' , $id , PDO::PARAM_INT );
and then execute

mysqli_stmt_bind_param - expects references [duplicate]

This question already has answers here:
mysqli bind_param() expected to be a reference, value given
(3 answers)
Closed 12 months ago.
I understand that we have to pass references to mysqli_stmt_bind_param. I am doing the following
$ref = refValues($data);
function refValues($arr){
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
var_dump(implode(",", $refs));
return $refs;
return $arr;
}
I am having all of my values in an array. I am using the above function to get the references. Got the above answer from SO
My PHP version is 5.6
I am binding the params in the following way.
mysqli_stmt_bind_param($stmt, $types, $ref);
$stmt is a statement created through mysqli_prepare. It returns error number 0.
$types is nothing but $types = str_pad('',count($data),'s');
I have verified $types data also. It returns expected number of types. i.e ssssssss
If I execute, I am getting the following error.
Only variables should be passed by reference in test.php
I found this solution in SO. I cannot assign 100 variables. I am not thinking that is feasible.
I found another alternative is call_user_func_arrary.
$values = refValues($data);
call_user_func_array(array($stmt, 'bind_param'), $values);
It returns number of bind type doesn't match number of values. It is weird for me. I have verified the array and values. Both counts are matching. I am not aware of internal implementation of call_user_func_array.
Please let me know is there any way to solve this efficiently.
This line
mysqli_stmt_bind_param($stmt, $types, $ref);
means that you have one reference to bind.
Why? Let's see:
first argument is a statement
second argument is a string with types
following arguments are references to values which should be binded.
As you pass one argument (it is $ref) - you are trying to bind only one value. And $ref is not a reference, it is array of values which are refernces. See the difference? Array of references vs reference.
So, you took second approach, and it is a right one:
$values = refValues($data);
call_user_func_array(array($stmt, 'bind_param'), $values);
What's the error here? You didn't pass types $types:
// do not assign anything to a variable
// pass results of `refValues` directly to `call_user_func_array`
call_user_func_array(array($stmt, 'bind_param'), array_merge(array($types), refValues($data)));
What do we do here: we are trying to call $stmt->bind_param and pass to this function arguments as array.
What are the arguments of $stmt->bind_param?
first argument is types ($types)
following arguments are references to values ($values)
Now it should work.
There are two possible ways to avoid this hassle:
Use PDO. Your current problem is only the first out of many WTFs you will have with mysqli. In this particular case it would be as simple and natural as
$stmt = $db->prepare($sql);
$stmt->execute($data);
Okay, you have such a whim of using mysqli. Then, as long as you are using a supported PHP version, you can use a splat or a three dot operator:
$stmt = $db->prepare($sql);
$types = str_repeat('s', count($data));
$statement->bind_param($types, ...$data);
$statement->execute();

Dynamically build a prepared statement with call_user_func_array() [duplicate]

This question already has answers here:
Build SELECT query with dynamic number of LIKE conditions as a mysqli prepared statement
(2 answers)
Closed 11 months ago.
I need to dynamically build up the SQL statement and the parameters based on user input. The length of the sql statement and the number of parameters changes based on user input. I am trying to use this tutorial and apply it to my code. Here is the code:
$query = "SELECT p.*, s.*
FROM product p
INNER JOIN product_shop ps
ON ps.p_id = p.p_id
INNER JOIN shop s
ON s.s_id = ps.s_id
WHERE s.country = ?";
$a_params[] = $place['country'];
$a_param_type[] = "s";
// prepare and bind
$param_type = '';
foreach ($place as $key => $value) {
if ($key === "country") {
continue;
}
$query .= " and s.$key = ?";
$a_params[] = $value;
$a_param_type[] = "s";
}
/* Prepare statement */
$stmt = $conn->prepare($query);
if($stmt === false) {
trigger_error('Wrong SQL: ' . $sql . ' Error: ' . $conn->errno . ' ' . $conn->error, E_USER_ERROR);
}
$a_params[] = $a_param_type;
/* use call_user_func_array, as $stmt->bind_param('s', $param); does not accept params array */
call_user_func_array(array($stmt, 'bind_param'), $a_params);
/* Execute statement */
$stmt->execute();
$meta = $stmt->result_metadata();
I know $place['country'] will always be populated. The sql statement is correct. It is:
"SELECT p.*, s.* \n FROM product p \n INNER JOIN product_shop ps \n ON ps.p_id = p.p_id \n INNER JOIN shop s \n ON s.s_id = ps.s_id \n WHERE s.country = ? and s.suburb = ? and s.city = ? and s.province = ?"
Don't mind the "\n" chars, they have no effect on the sql statement.
In:
call_user_func_array(array($stmt, 'bind_param'), $a_params);
the value of $a_params is:
0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:array(4)
0:"s"
1:"s"
2:"s"
3:"s"
In:
$meta = $stmt->result_metadata();
the value of $meta becomes:
current_field:0
field_count:13
lengths:null
num_rows:0
type:1
Meaning that no rows were selected from the database. I have executed this sql on the database manually and it returns rows. What is wrong with my code, that makes it return no rows from the database?
EDIT: I saw that this answer says to put the "ssss" at the start of the $params so I did that and got this error in the $stmt object:
errno:2031
error:"No data supplied for parameters in prepared statement"
error_list:array(1)
propertyNode.hasAttribute is not a function
I don't understand what ways you've tried, but I will try to answer:
according to bind_param manual:
first argument of bind_param is a string, like 'ssss'.
second and other arguments - are values to be inserted into a query.
So, your $a_params array should be not
0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:array(4)
0:"s"
1:"s"
2:"s"
3:"s"
But:
0:"ssss"
1:"New Zealand"
2:"Grey Lynn"
3:"Auckland"
4:"Auckland"
See? All values are strings. And placeholders' types are the first one.
Also take into consideration that order of arguments in $a_params must be the same as order of parameters in bind_param. This means that, i.e., $a_params like
0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:"ssss"
is wrong. Because first element of $a_params will be the first argument of bind_param and in this case it's not a "ssss" string.
So, this means that after you filled $a_params with values, placeholders' string should be added to the beginning of $a_params, with array_unshift for example:
// make $a_param_type a string
$str_param_type = implode('', $a_param_type);
// add this string as a first element of array
array_unshift($a_params, $str_param_type);
// try to call
call_user_func_array(array($stmt, 'bind_param'), $a_params);
In case this didn't work, you can refer to a part of answer you provided, where values of $a_params are passed by reference to another array $tmp, in your case you can try something like:
// make $a_param_type a string
$str_param_type = implode('', $a_param_type);
// add this string as a first element of array
array_unshift($a_params, $str_param_type);
$tmp = array();
foreach ($a_params as $key => $value) {
// each value of tmp is a reference to `$a_params` values
$tmp[$key] = &$a_params[$key];
}
// try to call, note - with $tmp, not with $a_params
call_user_func_array(array($stmt, 'bind_param'), $tmp);
REMEMBER: The second parameter of call_user_func_array needs to be referenced, not just a normal array. This is key. The accepted answer is good but just missing one thing, making the parameter referenced:
function makeValuesReferenced($arr){
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
$stmt = $conn->prepare($query);
//$stmt->bind_param($queryParamTypes, $queryParams);
call_user_func_array(array($stmt, 'bind_param'), makeValuesReferenced($queryParams));
$stmt->execute();

Error only on server with PDO: "Only variables can be passed by reference"/"Cannot pass parameter 2 by reference pdo"

I'm getting the following errors when trying to loop through an array on my server to bind statements with PDO:
"Only variables can be passed by reference"
or
"Cannot pass parameter 2 by reference pdo"
Works fine on my local XAMPP.
Code:
$sql = rest of sql query etc.
$loop = 1;
foreach ($animal_array $ani)
{
if ($loop == 1) {$sql .= " WHERE animal.id= :animal_id".$loop; }
else {$sql .= " OR animal.id= :animal_id".$loop;}
$loop++;
}
$stmt = $crud->db->prepare($sql);
$loop = 1;
foreach ($animal_array as $ani)
{
$stmt->bindParam(':animal_id'.$loop, $ani['animal_id'], PDO::PARAM_STR);
$loop++;
}
Also tried this at the end as I read somewhere that concatonations sometimes aren't liked:
foreach ($animal_array as $ani)
{
$ref = ":animal_id".$loop;
$stmt->bindParam($ref, $ani['animal_id'], PDO::PARAM_STR);
$loop++;
}
.
EDIT:
The array contains other values, sort of like this:
$animal_array['animal_id'];
$animal_array['name'];
$animal_array['colour'];
$animal_array['quantity']; etc
You ought to use bindValue for pure input parameters. The bindParam method is reserved for binding variables which can be used as input but also return query results (output).
Though that doesn't explain your issue. But we don't know what your array contains.
It's not the key generation / concatenation for sure.
It feels like you're overcomplicating matters... why not just pass the array to execute directly:
$sql .= 'WHERE animal.id IN ('
. '?'.str_repeat(',?', count($animal_array['animal_id'])-1)
. ')';
$qry = $crud->db->prepare($sql);
$qry->execute($animal_array['animal_id']);

Categories