I can convert the echo'ed output in to an SQL statement that executes in phpMyAdmin going...
From this:
INSERT INTO crumbs (ip_address,ip_address_2,device_info,user_id,connections) VALUES(?,?,?,?,?)Value:'00.000.000.000', '00.000.000.000', 0,0000, 1
Into this:
INSERT INTO crumbs (ip_address,ip_address_2,device_info,user_id,connections) VALUES('00.000.000.000', '00.000.000.000', 0,0000, 1)
It inserts the data in to the DB, no errors, however it executes through PHP-PDO...
With:
SQLSTATE[HY093]: Invalid parameter number
The code:
$columns = '('.implode(',', array_keys($user_connection)).''.",user_id,connections)";
$inserts="(".implode(',',array_fill(0,count($user_connection)+2, '?')).")";
$values = implode(', ',($user_connection)).",$user_id, 1";
$sql_insert = "INSERT INTO crumbs ".$columns." VALUES".$inserts;
$stmt = $this->_db->prepare($sql_insert);
$stmt->execute(array($values));
Edit-Adding $user_connection
$user_connection [ 'ip_address'] = "'".$_SERVER['REMOTE_ADDR']."'";
$user_connection [ 'ip_address_2']="'".$_SERVER['HTTP_X_FORWARDED_FOR']."'";
$user_connection ['device_info']=0;
The error occurs during the execution of the SQL code. I've gone over all the examples and found nothing that's equivalent, I'm thinking it's something simple I'm missing (a rule?) since the code executes locally.
You have to do something like this:
// ..code..
$values = $user_connection;
$values[] = $user_id;
$values[] = 1;
// ..code..
$stmt->execute($values);
Explanation of the problem:
When you have multiple ? placeholders, you can pass each value to be bounded as the values of an array (See Example #3 from the manual).
Now, since you are using implode, $values will be a single string, something like
'192.168.0.1', '8.8.8.8', 0, 'userid', 1
That means that when you call execute(array($values)), it will, in fact, bound it like this (representation-only, it's not really like this)
INSERT INTO crumbs (ip_address,ip_address_2,device_info,user_id,connections) VALUES ("'192.168.0.1', '8.8.8.8', 0, 'userid', 1", ?, ?, ?, ?)
because you only sent and array that has one value: the implosion of the other array. Since you didn't provide the same amount of values (1) as you have placeholders (5), you end up with
Invalid parameter number
Related
on the frontend I am using JQuery Validate, and I pass the following to the backend
data: {
'field1': $("input[name='field1']:checked").val(),
'field2': $("input[name='field2']:checked").val(),
'field3': $("#field3").val(),
'field4' : $("#field4").val(),
'field5' : $("#field5").val(),
'field6' : $("#field6").val()
}
The first three fields are required, so they will always have a value. The next three are optional, so they may not have a value. In my PHP file, I do something like this for required fields
if (!empty($_POST["field1"])) {
$field1 = filter_var($_POST["field1"], FILTER_SANITIZE_STRING);
array_push($inputArray, $field1);
} else{
$errors['field1'] = 'Please select field1';
}
And for optional fields I do
if (!empty($_POST['field4'])) {
$field4 = filter_var($_POST["field4"], FILTER_SANITIZE_STRING);
array_push($inputArray, $field4);
}
By the end of this, I have an $inputArray which may contain between 3-6 values which is passed to my database file. Here, I am doing something like this
$stmt = $dbh->prepare("INSERT INTO database_table(Field1, Field2, Field3, Field4, Field5, Field6) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->bindParam(1, $this->inputArray[0]);
Now that will be fine for the first three, but if I then try an optional element and its not there, an error will be thrown.
Whats the best way to handle this type of situation? In the PHP file, should I always push an empty value for the three if statements that are optional fields e.g.
else {
array_push($inputArray, '');
}
I know off several solutions I could potentially use, just wanted to get opinions from others as to how they would handle it.
Thanks
I've found that the best way of handling this sort of problem is to dynamically build the query. This sort of approach is easier when you use an associative $inputArray. As such, instead of doing
array_push($inputArray, $field1);
Do
$inputArray['field1Name'] = $field1;
replacing "field1" with the appropriate value for each field.
That way you can build your query like this:
$qry = "INSERT INTO database_table(";
$qry .= implode(',', array_keys($inputArray)); //append a comma separated list of field names.
$qry .= ") VALUES("
$qry .= trim(str_repeat('?,', count($inputArray)), ','); //append ?, for each element in the array and trim the trailing comma
$qry .= ')';
$stmt = $dbh->prepare($qry);
$stmt->execute( array_values($inputArray)); //Execute the query with the values from the input array
In this way the number of arguments in the sql query is dynamic and based on the number of fields that were filled out.
This could be easily changed to use named parameters instead of ? but the general concept is the same.
My previous question was closed because they said it was a duplicate but the duplicate posts did not answer my question. So here I go again and I put some additional comments in the edit section to state why the duplicate posts did not help me.
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 uncover 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);
// I've 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.
This question is different for two reasons
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 written differently for insert than for select or update statements.
//UPDATED CODE
global $dbconnection;
if(!$dbconnection){
die("Function wm_dynamicForm connection failed.</br>");
} else {
//echo "</br>Function wm_connectionToDatabase connection success</br>";
}
$db = mysqli_stmt_init($dbconnection);
$preparedQs = substr($preparedQs, 0, -2); //removes the end , from my string
$cleanstr = substr($cleanstr, 0, -2); //removes the end , from my string
$cleanstr = explode(", ", $cleanstr);
$ct = 0;
foreach ( $cleanstr as $cl){
$items[] = array(
'a'.$ct => $cl,
);
$ct = $ct + 1;
}
$stmt = mysqli_stmt_prepare($db, "INSERT INTO Contacts VALUES (". $preparedQs. ")");
mysqli_stmt_bind_param($db, $preparedType, ...$items);
mysqli_stmt_execute($stmt);
if(!mysqli_stmt_execute($stmt)){
echo "Error: ".mysqli_error($db);
}
You could do dynamic bind with php 5.6 feature called unpacking operator/elipsies the ....
$db = mysqli_connect('localhost', 'root', 'pass', 'database');
$data = array('name' => 'foo', 'age' => 99, 'email' => 'abc#abc.com');
$stmt = mysqli_stmt_prepare($db, "INSERT INTO Contacts VALUES (". $preparedQs. ")");
mysqli_stmt_bind_param($db, $preparedType, ...$data);
mysqli_stmt_execute($stmt);
I've been here before, dynamic prepared statement, dynamic query preparation.
The problem is not your code so far, the problem is the array of your sql fields you dynamically prepared to bind.
The index of that array starts with zero(0), but the index of your bindValue needs to start with one(1).
So what you will do is to make your field array index to start with 1.
In php you can force defaul index of an array to start with 1 instead of zero.
If am not wrong you have:
dbfield="username, password, name"
dbvalue="?, ?, ?"
and you have an array of input value you are looping with like:
foreach($inputarray as $key=>$value){
// key index must start from 1
//now you can bind
bindValue($key, $value);
}
If am flowing hit me answer accept.
try to use like this in prepared statement.
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test";
$conn = new mysqli($servername, $username, $password, $dbname);
$cleanstr = "John,Dew,Doe,Sr.";
$cleanstr = explode(",", $cleanstr);
$varstr=array();
foreach($cleanstr as $cl){
$varstr[] = "$".$cl;
}
$operation = "INSERT INTO Contacts (firstname, middlename, lastname, suffix) VALUES (?, ?, ?, ?)";
$callfunc = insertCommon($conn,$varstr, $operation);
function insertCommon($conn,$varstr, $operation){
$types = "";
foreach($varstr as $value)
$types .= "s";
$varstr = array_merge(array($types),$varstr);
$insertQry = $conn->prepare($operation);
$refArray = array();
foreach($varstr as $key => $value) $refArray[$key] = &$varstr[$key];
call_user_func_array(array($insertQry, 'bind_param'), $refArray);
$insertQry->execute();
return true;
}
I'm doing some cleanup and transformation on data (that part is done, whew), and need to insert it into a MySQL table. Having done this kind of thing in Perl previously, I assumed that, as part of processing, it would make sense for me to structure the data as an associative array with the keys being the same as the field names I need to load them into - that way, it would be easy to construct a prepared statement simply by looping over the keys and producing a list of both the named placeholders and the matching values.
However, I can't seem to make this work in PHP/PDO. Test code:
$x = <<<EOD
1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
4 4 4 4 4
EOD;
$fields = array('name', 'job', 'wallet_size', 'inseam', 'pet_name');
foreach(explode("\n", $x) as $line){
$data = array_combine($fields, explode(' ', $line));
# print_r($data);
$stmt = $dbh->prepare('INSERT INTO foobar VALUES('.':'.implode(', :', $fields));
foreach($fields as $field){
$stmt->bindParam(':'.$field, $data[$field]);
}
$stmt->execute();
}
Frankly, it feels too... graceless and hacky to work - and it doesn't.
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 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' in tst.php:24
There is a correct way to do this, right? I'd appreciate it if someone would introduce me to the appropriate PHP-flavored phrasing for it.
It's hard to debug SQL when you're staring at the PHP code that formats SQL, instead of the final SQL string itself. I suggest you always create a string variable so you can output it during debugging.
$sql = 'INSERT INTO foobar VALUES('.':'.implode(', :', $fields);
echo "$sql\n";
$stmt = $dbh->prepare($sql);
Outputs:
INSERT INTO foobar VALUES(:name, :job, :wallet_size, :inseam, :pet_name
Now it's very easy to see that you forgot the closing ) at the end of that INSERT statement!
Also, your use of PDO is more difficult than it needs to be. You don't need to use named parameters. You don't need to use bindParam(). Here's how I would write this code:
$fields = array('name', 'job', 'wallet_size', 'inseam', 'pet_name');
$columns = implode(',', $fields);
$placeholders = implode(',', array_fill(1, count($fields), '?'));
$sql = "INSERT INTO foobar ($columns) VALUES ($placeholders)";
echo "$sql\n"; // use this during debugging
$stmt = $dbh->prepare($sql);
foreach(explode("\n", $x) as $line){
$param_values = explode(' ', $line);
$stmt->execute($param_values);
}
Tips:
Prepare the query once, and re-use the prepared statement for each row of data you want to insert.
Pass an array of data values as an argument to execute(). This is easier than using bindParam().
Use positional parameter placeholders (?) instead of named parameter placeholders when your data is in a simple array instead of an associative array.
I have a PDO prepared statement in which the bound variables are prepared dynamically (they can vary from call to call) in an advanced search function on our site.
I know the actual SQL call is correct but for some reason I am getting the following error when trying to pass my string variable into the prepared statement:
SQLSTATE[HY093]: Invalid parameter
number: number of bound variables does
not match number of tokens
I have had this error before and am very familiar with the normal resolution steps. However, my circumstances are quite strange. With the following sample code:
$columns = "FirstName, LastName, ID, City, State";
$sWhere = "WHERE (FirstName LIKE ? AND LastName
LIKE ? AND ID LIKE ? AND City
LIKE ? AND State LIKE ?)";
$sVal = "'tom', 'lastname', '12345', 'Diego', 'CA'";
$sql = "SELECT ".$columns." FROM table ".$sWhere;
$stmt = $db->prepare($sql);
$stmt->execute(array($sVal));
where $sVal can range from 'firstname', 'lastname'.... to over 12 variables. Changing the number of variables has the same result. The complete statement is:
SELECT FirstName, LastName, ID, City, State
FROM table
WHERE (FirstName LIKE ? AND LastName
LIKE ? AND ID LIKE ? AND City
LIKE ? AND State LIKE ?)
When I run my query as is, the error above is returned. When I thought I did in fact have an incorrect number of variables, I ran an ECHO on my $value statement and found they did match.
As a secondary test, I took the output from the echo of $value and plugged directly back into the execute array:
$stmt->execute(array('tom', 'lastname', '12345', 'Diego', 'CA'));
This works with any issue at all.
It does not affect my question but I also placed % symbols within my $sVal variable for correctness:
$sVal="'%tom%', '%lastname%', '%12345%', '%Diego%', '%CA%'";
It makes ZERO sense to me that the echo'd output of the SAME variable would work but the variable itself would not. Any ideas?
Your $sVal is not an array, it's just a simple string, so when you write array($sVal), the execute() sees only one value.
You need to explode() your $sVal string to become an array:
// clean up the unnecessary single quotes and spaces
$value = str_replace(array("'", ", "), array("", ","), $value);
// make the array of the values
$value = explode(',', $value);
$stmt->execute($value);
The problem is that execute accepts an array of parameters, with each parameter having its own key. Passing a SQL-like, comma-separated string will not work, and even if it did, it would render PDO useless.
This is wrong:
$sVal = "'tom', 'lastname', '12345', 'Diego', 'CA'";
This is how it is supposed to be done:
$sVal = array('tom', 'lastname', '12345', 'Diego', 'CA');
Per example, if you are receiving data from a form in POST, it would be:
$sVal = array(
$_POST['firstname'],
$_POST['lastname'],
$_POST['zipcode'],
$_POST['city'],
$_POST['state'],
);
$stmt->execute($sVal);
How would you write a prepared MySQL statement in PHP that takes a differing number of arguments each time? An example such query is:
SELECT `age`, `name` FROM `people` WHERE id IN (12, 45, 65, 33)
The IN clause will have a different number of ids each time it is run.
I have two possible solutions in my mind but want to see if there is a better way.
Possible Solution 1 Make the statement accept 100 variables and fill the rest with dummy values guaranteed not to be in the table; make multiple calls for more than 100 values.
Possible Solution 2 Don't use a prepared statement; build and run the query checking stringently for possible injection attacks.
I can think of a couple solutions.
One solution might be to create a temporary table. Do an insert into the table for each parameter that you would have in the in clause. Then do a simple join against your temporary table.
Another method might be to do something like this.
$dbh=new PDO($dbConnect, $dbUser, $dbPass);
$parms=array(12, 45, 65, 33);
$parmcount=count($parms); // = 4
$inclause=implode(',',array_fill(0,$parmcount,'?')); // = ?,?,?,?
$sql='SELECT age, name FROM people WHERE id IN (%s)';
$preparesql=sprintf($sql,$inclause); // = example statement used in the question
$st=$dbh->prepare($preparesql);
$st->execute($parms);
I suspect, but have no proof, that the first solution might be better for larger lists, and the later would work for smaller lists.
To make #orrd happy here is a terse version.
$dbh=new PDO($dbConnect, $dbUser, $dbPass);
$parms=array(12, 45, 65, 33);
$st=$dbh->prepare(sprintf('SELECT age, name FROM people WHERE id IN (%s)',
implode(',',array_fill(0,count($parms),'?'))));
$st->execute($parms);
There is also the FIND_IN_SET function whose second parameter is a string of comma separated values:
SELECT age, name FROM people WHERE FIND_IN_SET(id, '12,45,65,33')
decent sql wrappers support binding to array values.
i.e.
$sql = "... WHERE id IN (?)";
$values = array(1, 2, 3, 4);
$result = $dbw -> prepare ($sql, $values) -> execute ();
Please take #2 off the table. Prepared statements are the only way you should consider protecting yourself against SQL injection.
What you can do, however, is generate a dynamic set of binding variables. i.e. don't make 100 if you need 7 (or 103).
I got my answer from: http://bugs.php.net/bug.php?id=43568.
This is my working mysqli solution to my problem. Now I can dynamically use as many parameters as I want. They will be the same number as I have in an array or as in this case I am passing the ids from the last query ( which found all the ids where email = 'johndoe#gmail.com') to the dynamic query to get all the info about each of these id no matter how many I end up needing.
<?php $NumofIds = 2; //this is the number of ids I got from the last query
$parameters=implode(',',array_fill(0,$NumofIds,'?'));
// = ?,? the same number of ?'s as ids we are looking for<br />
$paramtype=implode('',array_fill(0,$NumofIds,'i')); // = ii<br/>
//make the array to build the bind_param function<br/>
$idAr[] = $paramtype; //'ii' or how ever many ?'s we have<br/>
while($statement->fetch()){ //this is my last query i am getting the id out of<br/>
$idAr[] = $id;
}
//now this array looks like this array:<br/>
//$idAr = array('ii', 128, 237);
$query = "SELECT id,studentid,book_title,date FROM contracts WHERE studentid IN ($parameters)";
$statement = $db->prepare($query);
//build the bind_param function
call_user_func_array (array($statement, "bind_param"), $idAr);
//here is what we used to do before making it dynamic
//statement->bind_param($paramtype,$v1,$v2);
$statement->execute();
?>
If you're only using integer values in your IN clause, there's nothing that argues against constructing your query dynamically without the use of SQL parameters.
function convertToInt(&$value, $key)
{
$value = intval($value);
}
$ids = array('12', '45', '65', '33');
array_walk($ids, 'convertToInt');
$sql = 'SELECT age, name FROM people WHERE id IN (' . implode(', ', $ids) . ')';
// $sql will contain SELECT age, name FROM people WHERE id IN (12, 45, 65, 33)
But without doubt the solution here is the more general approach to this problem.
I had a similiar problem today and I found this topic. Looking at the answers and searching around the google I found a pretty solution.
Although, my problem is a little bit more complicated. Because I have fixed binding values and dynamic too.
This is the mysqli solution.
$params = array()
$all_ids = $this->get_all_ids();
for($i = 0; $i <= sizeof($all_ids) - 1; $i++){
array_push($params, $all_ids[$i]['id']);
}
$clause = implode(',', array_fill(0, count($params), '?')); // output ?, ?, ?
$total_i = implode('', array_fill(0, count($params), 'i')); // output iiii
$types = "ss" . $total_i; // will reproduce : ssiiii ..etc
// %% it's necessary because of sprintf function
$query = $db->prepare(sprintf("SELECT *
FROM clients
WHERE name LIKE CONCAT('%%', ?, '%%')
AND IFNULL(description, '') LIKE CONCAT('%%', ?, '%%')
AND id IN (%s)", $clause));
$thearray = array($name, $description);
$merge = array_merge($thearray, $params); // output: "John", "Cool guy!", 1, 2, 3, 4
// We need to pass variables instead of values by reference
// So we need a function to that
call_user_func_array('mysqli_stmt_bind_param', array_merge (array($query, $types), $this->makeValuesReferenced($merge)));
And the function makeValuesreferenced:
public function makeValuesReferenced($arr){
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
Links for getting this 'know-how': https://bugs.php.net/bug.php?id=49946, PHP append one array to another (not array_push or +), [PHP]: Error -> Too few arguments in sprintf();, http://no2.php.net/manual/en/mysqli-stmt.bind-param.php#89171, Pass by reference problem with PHP 5.3.1