This question already has answers here:
Use an array in a mysqli prepared statement: `WHERE .. IN(..)` query [duplicate]
(8 answers)
Build SELECT query with dynamic number of LIKE conditions as a mysqli prepared statement
(2 answers)
Closed 11 months ago.
I'm currently facing a difficulty where putting a comma-separated values to a MySQL NOT IN doesn't give me the result I was hoping for. There must be something I'm missing as I'm unsure what to search for this particular problem. Running only the MySQL code works, but passing the parameter from another PHP function didn't.
Here's the code that's giving me a problem:
$uid = 1;
$selected_uids = '1,2';
$result = $db->retrieveFollowingWithCondition($uid, $selected_uids);
...then somewhere along the code...
public function retrieveFollowingWithCondition($uid, $selected_uids) {
$stmt = $this->conn->prepare("SELECT *
FROM `friendlist`
WHERE `uid` = ? AND `buddy_uid` NOT IN (?)
GROUP BY `buddy_uid`;");
$stmt->bind_param("is", $uid, $selected_uids);
...}
I've tested just putting '2' in $selected_uids and it actually works. But once there's comma involved, the code runs but the $selected_uids are still in the result. Not sure this is a bad practice or just needing a minor adjustment to the code. Anyway, I'm really looking forward to understand why it's not working for me.
By using s in bind_param you are telling PHP to treat the entire contents of $selected_uids as a string. Therefore, "1,2" is treated as ('1,2') instead of (1,2). Your problem is that bind_param doesn't support arrays, so support of IN queries is limited. There are a number of alternatives to get around this limitation, but since you are dealing with a list of ints, I would probably do a raw string concat.
// using is_numeric because is_int("1") === false
$filtered = array_filter('is_numeric', $selected_uids);
// You could also just call array_map('intval', $selected_uids);
// Depending on your needs.
if(!$filtered) {
return; // No valid values
}
$filteredStr = implode(',', $filtered);
$stmt = $this->conn->prepare("SELECT *
FROM `friendlist`
WHERE `uid` = ? AND `buddy_uid` NOT IN ($filteredStr)
GROUP BY `buddy_uid`;");
Should also be noted: if I were trying to use strings for an IN query, I would likely do the following:
$filtered = array_map([$this->conn, 'escape_string'], $queried);
$inQuery = '\'' . implode('\',\'', $filtered) . '\'';
I find that notation cleaner and easier than a dynamically generated bind_param format string.
You should bind every parameter in IN(...) separately, but method bind_param doesn't support multiple calls. There is a nice class that can do this and you can find it on PHP documentation pages:
Custom class for multiple bind_param
Related
This question already has answers here:
Use one bind_param() with variable number of input vars
(5 answers)
Dynamically bind params in $bind_param(); Mysqli
(2 answers)
Closed 5 years ago.
I am trying to construct a prepared statement dynamically and I keep getting the following error:
mysqli_stmt_bind_param(): Number of elements in type definition string
doesn't match number of bind variables in
When I echo my statements the number of type definitions matches the bind variable so I don't know what is wrong. I think my code may be passing in strings, quotes or something instead of variables but I'm new to prepared statement and not sure how to check my query. When using simple mysqli_query I can echo the query and see were my error is at. I'm not sure how to do this with prepared statements so I'm hoping someone can help me with uncovering my error.
I am trying to construct the prepares statement dynamically so that I can reuse the code as follows:
$db = mysqli_stmt_init($dbconnection);
//i have looped through my fields and constructed a string that when echoed returns this: ?, ?, ?, ?, I use sub str just to remove the last comma and space leaving me with the string ?, ?, ?, ?. Ive echoed this to the browser to make sure it is correct.
$preparedQs = substr($preparedQs, 0, -2);
//I then loop through each field using their datatype and constructs the type string as follows ssss. Ive echoed this to the browser to make sure it is correct.
$preparedType = 'ssss';
//I then loop through my post array verifying and cleaning the data and then it constructing a string of clean values that results in Mike, null, Smith, Sr., (First, Middle, Last, Suffix) I use substr again just to remove the last comma and space. Ive echoed this to the browser to make sure it is correct.
$cleanstr = substr($cleanstr, 0, -2);
//I then explode that string into a an array that I can loop through and assign/bind each value to a variable as follows and use substr again to remove last comma and space.
$cleanstr = explode(", ", $cleanstr);
$ct2 = 0;
foreach ( $cleanstr as $cl){
$name = "a".$ct2;
$$name = $cl;
$varstr .= "$".$name.", ";
$ct2 = $ct2 +1;
}
$varstr = substr($varstr, 0, -2);
//ive echoed the $varstr to the browser and get $a1, $a2, $a3, $a4. I have also echo their value outside of the loop and know values have been assigned.
//I then try to assign each step above the appropriate prepared statement place holder
$stmt = mysqli_stmt_prepare($db, "INSERT INTO Contacts VALUES (". $preparedQs. ")");
mysqli_stmt_bind_param($db, "'".$preparedType."'", $varstr);
mysqli_stmt_execute($stmt);
I'm am not sure what I am doing wrong because when I echo $preparedQs, $preparedType and $varstr they all have the same number of elements yet I'm getting the "mysqli_stmt_bind_param(): Number of elements in type definition string doesn't match number of bind variables in.." error. All i can think is that I have quotes or something where I shouldn't but I've tried adding and removing quotes in certain areas and cant get the error to resolve.
Also, I read some posts about passing null in prepared statement but even when I replace the null with an actual value, I still get the same error.
It's probably worth noting that when using simple procedural mysqli_query and mysqli_real_escape_string to clean my data things work fine. I am trying to improve my security by converting my application to prepared statement simply for the added security.
// EDIT / DIFFERENT
This question is different for two reason
I am using procedural coding and not object or PDO. So being new to prepared statements, the examples given aren't helpful even after trying to make sense of them.
I am using an insert statement, not a select or update statement which in procedural php the query string is writen differently for insert than for select or update statements.
Will someone help me with the procedural insert solution to my problem. Thank you.
This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 7 years ago.
Is it possible to use prepared statements with either MySQLi or PDO and still be able to dynamically add items to the IN part of the query, for example...
$somearray = ['tagvalue1', 'tagvalue2', 'tagvalue3'];
$sql = "SELECT foo FROM bar
WHERE tag IN(?)";
I ask this because I have a situation whereby the number of elements in the IN part is not known until runtime.
You asked:
Is it possible to use prepared statements with either MySQLi or PDO
and still be able to dynamically add items to the IN part of the
query, for example...
No, unfortunately it is not. It happens that ColdFusion does this, but not php.
While you can't do exactly what you want with a prepared query, you can dynamically generate the $sql string for the query to accomplish the same thing.
Given some array $array = (n, n1, n2, ... nN)
$sql = "SELECT foo FROM bar WHERE tag IN (";
foreach($array as $value) {
$sql .= "'" . $value . "', ";
}
// Strip off the last comma and space from the IN clause
$sql = substr($sql, 0, strlen($sql) - 2);
$sql .= ")";
It's certainly not the most elegant solution and you'll have to do some more data validation or escaping of dangerous characters that a prepared query would handle better, but it will do the job.
As a side note, there are ORM (Object Relational Mapper) libraries that support things like accepting an array of values to generate the IN clause in a database statement. Propel is the one I have most experience with, but I'm sure others like Doctrine would have a similar method.
A propel-ish example would be like
$results = BarQuery::create()
->select('foo')
->filterByTag(array($value1, $value2, ..., $vauleN)
->find();
Lots of added functionality and support, but does increase your initial set up time for a project.
I want to make a "dynamic" WHERE clause in my query based on a array of strings. And I want to run the created query using Mysqi's prepared statements.
My code so far, PHP:
$searchArray = explode(' ', $search);
$searchNumber = count($searchArray);
$searchStr = "tags.tag LIKE ? ";
for($i=1; $i<=$searchNumber-1 ;$i++){
$searchStr .= "OR tags.tag LIKE ? ";
}
My query:
SELECT tag FROM tags WHERE $searchStr;
More PHP:
$stmt -> bind_param(str_repeat('s', count($searchArray)));
Now this obviously gives me an error since the bind_param part only contains half the details it need.
How should I proceed?
Are there any other (better) way of doing this?
Is it secure?
Regarding the security part of the question, prepared statements with placeholders are as secure as the validation mechanism involved in filling these placeholders with values up. In the case of mysqli prepared statements, the documentation says:
The markers are legal only in certain places in SQL statements. For example, they are allowed in the VALUES() list of an INSERT statement (to specify column values for a row), or in a comparison with a column in a WHERE clause to specify a comparison value.
However, they are not allowed for identifiers (such as table or column names), in the select list that names the columns to be returned by a SELECT statement, or to specify both operands of a binary operator such as the = equal sign. The latter restriction is necessary because it would be impossible to determine the parameter type. It's not allowed to compare marker with NULL by ? IS NULL too. In general, parameters are legal only in Data Manipulation Language (DML) statements, and not in Data Definition Language (DDL) statements.
This clearly excludes any possibility of modifying the general semantic of the query, which makes it much harder (but not impossible) to divert it from its original intent.
Regarding the dynamic part of your query, you could use str_repeat in the query condition building part, instead of doing a loop:
$searchStr = 'WHERE tags.tag LIKE ?' .
str_repeat($searchNumber - 1, ' OR tags.tag LIKE ?');
For the bind_param call, you should use call_user_func_array like so:
$bindArray[0] = str_repeat('s', $searchNumber);
array_walk($searchArray,function($k,&$v) use (&$bindArray) {$bindArray[] = &$v;});
call_user_func_array(array($stmt,'bind_param'), $bindArray);
Hopefully the above snippet should bind every value of the $bindArray with its corresponding placeholder in the query.
Addenum:
However, you should be wary of two things:
call_user_func_array expects an integer indexed array for its second parameter. I am not sure how it would behave with a dictionary.
mysqli_stmt_bind_param requires its parameters to be passed by reference.
For the first point, you only need to make sure that $bindArray uses integer indices, which is the case in the code above (or alternatively check that call_user_func_array doesn't choke on the array you're providing it).
For the second point, it will only be a problem if you intend to modify the data within $bindArray after calling bind_param (ie. through the call_user_func_array function), and before executing the query.
If you wish to do so - for instance by running the same query several times with different parameters' values in the same script, then you will have to use the same array ( $bindArray) for the following query execution, and update the array entries using the same keys. Copying another array over won't work, unless done by hand:
foreach($bindArray as $k => $v)
$bindArray[$k] = some_new_value();
or
foreach($bindArray as &$v)
$v = some_new_value();
The above would work because it would not break the references on the array entries that bind_param bound with the statement when it was called earlier. Likewise, the following should work because it does not change the references which have been set earlier up.
array_walk($bindArray, function($k,&$v){$v = some_new_value();});
A prepared statement needs to have a well-defined number of arguments; it can't have any element of dynamic functionality. That means you'll have to generate the specific statement that you need and prepare just that.
What you can do – in case your code actually gets called multiple times during the existence of the database connection - is make cache of those prepared statements, and index them by the number of arguments that you're taking. This would mean that the second time you call the function with three arguments, you already have the statement done. But as prepared statements don't survive the disconnect anyway, this really only makes sense if you do multiple queries in the same script run. (I'm deliberately leaving out persistent connections, because that opens up an entirely different can of worms.)
By the way, I'm not an MySQL expert, but would it not make a difference to not have the where conditions joined,but rather writing WHERE tags in (tag1, tag2, tag3, tag4)?
Solved it by the help of an answer found here.
$query = "SELECT * FROM tags WHERE tags.tag LIKE CONCAT('%',?,'%')" . str_repeat(" OR tags.tag LIKE CONCAT('%',?,'%')", $searchNumber - 1)
$stmt = $mysqli -> prepare($query);
$bind_names[] = str_repeat('s', $searchNumber);
for ($i = 0; $i < count($searchArray); $i++){
$bind_name = 'bind'.$i; //generate a name for variable bind1, bind2, bind3...
$$bind_name = $searchArray[$i]; //create a variable with this name and put value in it
$bind_names[] = & $$bind_name; //put a link to this variable in array
}
call_user_func_array(array($stmt, 'bind_param'), &$bind_names);
$stmt -> execute();
This question already has answers here:
How to include a PHP variable inside a MySQL statement
(5 answers)
Closed 3 years ago.
How do I escape quotes in PHP when trying to query a MySQL database?
Without adding addslashes on every value:
$fname = addslashes("Value's with quote''s'");
$lname = addslashes("Value's with quote''s'");
The proper way is using prepared statements, e.g. via PDO.
If you can't do that, you have to process all values which are passed into a database query with mysql_real_escape_string() - and no, doing that simply on all $_POST data is not an option since that would render them unusable for HTML output, etc. You could create a $_ESC or something similar though... but note that this variable will not be superglobal!
You ought to escape special characters (not only quotes) on every string value (it's useless to escape values you're not going to enclose in quotes in a query. Those values require another treatment).
To avoid boring repetitive typing you can apply an escaping function to array items in a loop.
In case you're using MySQL and for INSERT/UPDATE queries, you can use this helper function:
function dbSet($fields) {
$set = '';
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$set .= "`$field`='" . mysql_real_escape_string($_POST[$field]) . "', ";
}
}
return substr($set, 0, -2);
}
It is used like this:
$id = intval($_POST['id']);
$table = 'users';
$fields = explode(" ","name surname lastname address zip fax phone");
$query = "UPDATE `$table` SET ".dbSet($fields).", `date`=NOW() WHERE id=$id";
Also don't forget to set proper encoding using mysql_set_charset() as it's required for the mysql_real_escape_string() function.
A good idea would be using PDO prepared statements as described here.
It will automatically escape those characters.
Firstly, don't use addslashes() - it is not recommended for use with escaping DB query strings because it doesn't escape everything that actually needs to be escaped; there are some characters that can still get through.
The correct solution depends on the database you're using. Assuming you're using MySQL, the correct function to use instead of addslashes() is mysql_real_escape_string().
You probably notice that using this on every line is even more verbose than addslashes(), so it doesn't really answer your question.
If your fields are all separate variables (as per your example), then you're really stuck with doing that for a bunch of lines of code.
If you're using an array (eg $_POST), then you can do it in a loop, which will make things a lot neater - you can do things like this:
foreach($_POST as $key=>$value) {
$sqlstrings[$key]="`".$key"` = '".mysql_real_escape_string($value)."'";
}
$sql = "update table ".implode(' ',$sqlstrings)." where id=".$update_id;
A more up-to-date method for doing SQL is to use an object model rather than manually building the queries. PHP has a number of libraries that may help: mysqli is an improved MySQL library, and PDO is a database-neutral library. Either of these would give you much better security and flexibility than building the SQL code directly. However if you already have a lot of code in place then they would represent a fairly significant overhead of code changes, so you may want to go with the mysql_real_escape_string() option discussed above in the short term. I do recommend investating them them though.
This question already has answers here:
How can I bind an array of strings with a mysqli prepared statement?
(7 answers)
Closed 3 years ago.
I’m moving some old code over to the new msqli interface using prepared statements, I’m having trouble with SQL statements containing the IN clause. I would just normally do this:
$ids = '123,535,345,567,878'
$sql = "SELECT * FROM table WHERE id IN ($ids)";
$res = mysql_query($sql);
Converting this to mysqli and prepared statements I have tried a number of solutions:
$ids = '123,535,345,567,878'
$ids = implode($ids,',');
$result = $msqli->prepare("SELECT foo,blar FROM table WHERE id IN (?));
$result->bind_param("i", $ids);
$result->execute();
The above fails and calculating the number of elements in the array and altering number of question marks in the SQL string and calling bind_parm for each element in the array also fails. Just using the comma separated string also fails.
I can find no good documentation in Google on this, so how have you solved the problem?
It's not possible to bind a list of variable length to a single bound variable.
Similarly, if you were to bind the string $ids you'll actually end up with:
SELECT foo,blar FROM table WHERE id IN ('123,535,345,567,878')
(Note the quotes around the list of IDs).
Creating your own query with the right number of question marks and bound parameters should have actually worked - you may need to try that again and report on the actual error.
Alternatively, this may be one of those occasions where it's unfortunately necessary to hand-craft your own SQL and not use bound parameters.
Look at the answer to a similar question that has been asked here before (second code sample):
I have an array of integers, how do I use each one in a mysql query (in php)?
It boils down to:
create the SQL string with the right amount of question marks
use call_user_func_array() to bind your array to the query string
I thought the point of prepared statements was so in this situation you could just do:
$stmt = $this->mysqli->prepare("UPDATE radcheck SET attribute = ?, value = ? WHERE username = ? AND attribute LIKE 'CS-Total-Octets%'");
foreach ($usernames as $username)
{
$stmt->bind_param('sss', $bandwidth_types[$bandwidth_type], $bandwidth_bytes, $username);
$stmt->execute();
}
$stmt->close();