PHP Dynamic Prepared Statement - Number of Elements Not Matching [duplicate] - php

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.

Related

Comma-separated values doesn't work in PHP MYSQL [duplicate]

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

String of strings and PDO

What I'm trying to do is pass an array of content tags into my postgresql database query.
I've got this function:
public static function arrayToPdo($array, $pdocon)
{
foreach($array as &$val)
{
$val = $pdocon->quote($val);
}
return implode(",", $array);
}
So that converts the array into a string of quoted values.
I've echoed it and I get 'home','test' which are the tags I expected.
So then I want to bind the return value of that function in my PDO. Here's the where part of the query.
where #> array[:tags]
Now I've substituted the placeholder with the values 'home' and 'test' and it works so the problem lies with how the binding works I think.
When I do the following it works fine:
"where #> array[" . arrayToPdo($array, $conn) . "]"
So here's the code where I bind the value:
$st->bindvalue(":tags", arrayToPdo($array, $conn), PDO::PARAM_STR);
I have another value being bound correctly just before so I feel like it should work.
I've been messing with it for a day and a half and I can't figure it out.
Basically when I enter the quoted values into the query string it works; when I concatenate the function into the query string it works; but when I bind it it doesn't.
There are no errors, I only know it is not working because the query doesn't return any records. I haven't found a way to see what is being inserted into the query string when it is executed so I'm not sure what the problem is.
Any help here would be much appreciated.
In array[:tags], :tags is a set of values, not a single value. Only single values can be bound.
What you probably want here is to use a syntax for an array that represents it as a single value.
Here: PHP array to postgres array
you'll find a function that converts a PHP array into such a syntax.
Then the clause in your query should look like:
"... where #> cast(:myarray as text[])"
Note that there is no array constructor, it's a cast.
Bind it as
$st->bindvalue(":tags", to_pg_array($array), PDO::PARAM_STR);
making sure that PDO is configured to use real prepared statements:
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
(otherwise I suspect that PDO could mess when requoting the parameter. With true prepared statements, parameters are passed by the driver outside of the query and are not quoted).

Using prepared statements with SQLite3 and PHP

I'm trying to add data to a database using SQLite3 in PHP. I got it working without prepared statements but now I'm trying to make it safer. I'm not using PDO.
So far the following code doesn't work. It just inserts the words ":name" and ":email" into the database, instead of what their bound values should be:
$smt = $db->prepare("insert into names (name, email) values (':name', ':email')");
$smt->bindValue(':name', $var_name);
$smt->bindValue(':email', $var_email);
$var_name = ($_POST[post_name]);
$var_email = ($_POST[post_email]);
$smt->execute();
So I thought at first that this was because I have single quotes around :name and :email in the prepared statement. So I took those out. Now when I post the form, it just puts blank entries into the database, it doesn't insert the values of $var_name and $var_email
The statement is executing, it's just not binding the variables properly I don't think. What have I done wrong?
You managed to confuse binding functions.
It is bindParam have to be used if you don't have your variable assigned yet.
While bindValue have to be used with existing value only.
Also, you should turn error reporting ON
You don't need intermediate variables, you must do this:
$smt = $db->prepare("insert into names (name, email) values (':name', ':email')");
$smt->bindValue(':name', $_POST['post_name'], SQLITE3_TEXT);
$smt->bindValue(':email', $_POST['post_email'], SQLITE3_TEXT);
$smt->execute();
As documented in SQLite3Stmt::bindValue() value is binded instantly, not as SQLite3Stmt::bindParam() that gets the value of the variable at execute() time. So the problem is that that variables are empty when the statement is executed.
Remember:
You don't need to add parentheses on variable assignment: $a = ($b); -> $a = $b;
You MUST quote variable key name. Otherwise PHP will try to look for a constant with this name and will throw a warning if it doesn't exists... but will assign a erroneous key value if it exists!! $_POST[post_name] -> $_POST['post_name']

PHP/MySQL: Using array elements in WHERE clause using prepared statements

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();

How do you use IN clauses with mysqli prepared statements [duplicate]

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();

Categories