PDO unexpectedly throws bound variable errors with multiple bound parameters - php

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

Related

Unable to locate error with PDO prepared statement [duplicate]

I'm working on a dynamic query that uses variables to specify a table, a field/column, and a value to search for. I've gotten the query to work as expected without the variables, both in phpMyAdmin (manually typing the query) and from within the code by concatenating the variables into a complete query.
However, when I use bindParam() or bindValue() to bind the variables, it returns an empty array.
Here's my code:
function search_db($db, $searchTerm, $searchBy, $searchTable){
try{
$stmt = $db->prepare('
SELECT
*
FROM
?
WHERE
? LIKE ?
');
$stmt->bindParam(1, $searchTable);
$stmt->bindParam(2, $searchBy);
$stmt->bindValue(3, '%'. $searchTerm.'%');
$stmt->execute();
} catch(Exception $e) {
return array();
}
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// database initialization, creates the $db variable
require(ROOT_PATH . "include/database.php");
$matches = search_db($db, 'search term', 'myColumn', 'myTable');
var_dump($matches);
Expected results: an array of rows from the database
Actual results: an empty array
Unfortunately, placeholder can represent a data literal only. So, a very common pitfall is a query like this:
$opt = "id";
$sql = "SELECT :option FROM t";
$stm = $pdo->prepare($sql);
$stm->execute([':option' => $opt]);
$data = $stm->fetchAll();
This statement will return just a literal string 'id' in the fieldset, not the value of the column named id.
So, a developer must take care of identifiers oneself - PDO offers no help for this matter.
To make a dynamical identifier safe, one has to follow 2 strict rules:
to format identifiers properly
to verify it against a hardcoded whitelist.
To format an identifier, one has to apply these 2 rules:
Enclose the identifier in backticks.
Escape backticks inside by doubling them.
After such formatting, it is safe to insert the $table variable into query. So, the code would be:
$field = "`".str_replace("`","``",$field)."`";
$sql = "SELECT * FROM t ORDER BY $field";
However, although such formatting would be enough for the cases like ORDER BY, for most other cases there is a possibility for a different sort of injection: letting a user choose a table or a field they can see, we may reveal some sensitive information, like a password or other personal data. So, it's always better to check dynamical identifiers against a list of allowed values. Here is a brief example:
$allowed = array("name","price","qty");
$key = array_search($_GET['field'], $allowed);
$field = $allowed[$key];
$query = "SELECT $field FROM t"; //value is safe
For keywords, the rules are same, but of course there is no formatting available - thus, only whitelisting is possible and ought to be used:
$dir = $_GET['dir'] == 'DESC' ? 'DESC' : 'ASC';
$sql = "SELECT * FROM t ORDER BY field $dir"; //value is safe

Handling dynamic number of answers in prepared statement

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.

How to UPDATE a MySQL table from an HTML form with PHP PDO prepared statements

I’m working on an app and I am getting stuck updating values in a MySQL database table.
When I use the single value version of the name attribute (i.e. name="value1") everything works fine. But, when I use the array syntax (name="value[]") for the name attribute I get an error.
The error occurs right here:
$stmt = $conn->prepare("UPDATE students SET value=value+{$_POST['value']}");
The error is:
Error: Notice: Array to string conversion on line 8
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'value' in 'field list'
This is my PDOStatement::execute statement.
$stmt->execute(array("value", $_POST['value']));
This is my HTML:
<input class="input" id="id1" name="value[]" type="range">
<input class="input" id="id2" name="value[]" type="range">
<input class="input" id="id3" name="value[]" type="range">
The columns in my table are named value1, value2, value3 and so on.
Thanks for your comprehensive reaction. I didn't mentioned that the input must increase the database values. However, this is how I get it to work.
if(isset($_POST['value'])){
if(is_array($_POST['value'])) {
foreach($_POST['value'] as $key => $value) {
$stmt = $conn->prepare("UPDATE students SET value".($key+1)." = ? + value".($key+1)." ");
$stmt->execute(array($value));
}
}
With thanks to the guy who removed his answer but he give me the inspiration!
Warning: Never use anything from $_POST directly! Sanitize and validate all user input. What you see below is for demonstration purposes only. Without a WHERE clause in your query, the entire table will be updated.
When you do this in your HTML <input> elements:
name="value[]"
$_POST will receive an array for the $_POST element value. Thus,
$_POST['value'] //resolves to an array.
That is why you are getting an error (Error: Notice: Array to string conversion) when you do this:
$stmt = $conn->prepare("UPDATE `students` SET `value` = `value` +{$_POST['value']}");
Braces {} are typically used during string interpolations within double quotes. Your code is trying to turn an array in to a string.
You might try this instead:
$stmt = $conn->prepare("UPDATE `students` SET `value1` = `value1` + ?");
Or, this ...
$stmt = $conn->prepare("UPDATE `students` SET `value1` = `value1` + :value1");
Do not put user input directly into a prepared statement. The PHP PDO::prepare manual page states.
Prepares an SQL statement to be executed by the
PDOStatement::execute() method. The SQL statement can contain zero or
more named (:name) or question mark (?) parameter markers for which
real values will be substituted when the statement is executed. You
cannot use both named and question mark parameter markers within the
same SQL statement; pick one or the other parameter style. Use these
parameters to bind any user-input, do not include the user-input
directly in the query.
You need to do some processing of the array $_POST['values'] first. Sanitizing and validating the data is highly advisable and a best practice.
The data for your <input> elements is actually here.
$_POST['values'][0] //Resolves to a string.
$_POST['values'][1] //Resolves to a string.
$_POST['values'][2] //Resolves to a string.
The error Column not found: 1054 Unknown column 'value' in 'field list' suggests that you should use the name of an actual column in your table. By your own words, those field names are value1, value2, and value3.
When you use PDOStatement::execute, you can do this just to see if things are working in the ? way....
$stmt->execute([$_POST['values'][0]]) //PHP 5.4+
Or
$stmt->execute(array([$_POST['values'][0])) //PHP 5.3 and below
If you used named parameters, you can try this.
$stmt->execute(['value1' => $_POST['values'][0]]) //PHP 5.4+
Or
$stmt->execute(array('value1' => [$_POST['values'][0])) //PHP 5.3 and below
.... but I call that living dangerously. Sanitize and validate your inputs first.
If you ever get to the point of using MySQL stored procedures (say, for an e-commerce site) with your PDO prepared statements, look up PDOStatement::bindParam (must use variables with this) and PDOStatement::bindValue (can use general values, like string literals).
$stmt->bindValue(1, $variable);
$stmt->execute();
Or ...
$stmt->bindValue(:name, $variable);
$stmt->execute();
$stmt->bindParam(1, $variable); or $stmt->bindParam(:name, $variable); are mostly useful for using IN/OUT arguments with stored procedures.
Be careful. You will end up updating the entire table because there is no WHERE condition in your query! But, if you want all records to have the same value, that's how you can do it. :-) What would Edgar F. Codd say?
While it is possible to use a loop to solve the problem of using all three supposed $_POST['value'] elements, one still needs to ask why the entire table would need to be updated for each iteration.
I edited your answer to look like this.
if(isset($_POST['value']) && is_array($_POST['value']) && !empty($_POST['value']) && (count($_POST['value']) === 3)){
foreach($_POST['value'] as $key => $value) {
$num = ($key + 1);
$stmt = $conn->prepare("UPDATE `students` SET value{$num} = ? + value{$num}");
$stmt->bindValue(1, $value);
$stmt->execute();
}
}
While not comprehensive, at least better checking is being done. But, something like this could be better.
if(!isset($_POST['value']) || !is_array($_POST['value']) || empty($_POST['value']) || (count($_POST['value']) !== 3)){
throw new UnexpectedValueException("Something is wrong with the input in $_POST.");
}
foreach($_POST['value'] as $key => $value) {
$num = ($key + 1);
$stmt = $conn->prepare("UPDATE `students` SET value{$num} = ? + value{$num}");
$stmt->bindValue(1, $value);
$stmt->execute();
}

Error:sqlstate[hy093]: invalid parameter number

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

Can I use a PDO prepared statement to bind an identifier (a table or field name) or a syntax keyword?

I'm working on a dynamic query that uses variables to specify a table, a field/column, and a value to search for. I've gotten the query to work as expected without the variables, both in phpMyAdmin (manually typing the query) and from within the code by concatenating the variables into a complete query.
However, when I use bindParam() or bindValue() to bind the variables, it returns an empty array.
Here's my code:
function search_db($db, $searchTerm, $searchBy, $searchTable){
try{
$stmt = $db->prepare('
SELECT
*
FROM
?
WHERE
? LIKE ?
');
$stmt->bindParam(1, $searchTable);
$stmt->bindParam(2, $searchBy);
$stmt->bindValue(3, '%'. $searchTerm.'%');
$stmt->execute();
} catch(Exception $e) {
return array();
}
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// database initialization, creates the $db variable
require(ROOT_PATH . "include/database.php");
$matches = search_db($db, 'search term', 'myColumn', 'myTable');
var_dump($matches);
Expected results: an array of rows from the database
Actual results: an empty array
Unfortunately, placeholder can represent a data literal only. So, a very common pitfall is a query like this:
$opt = "id";
$sql = "SELECT :option FROM t";
$stm = $pdo->prepare($sql);
$stm->execute([':option' => $opt]);
$data = $stm->fetchAll();
This statement will return just a literal string 'id' in the fieldset, not the value of the column named id.
So, a developer must take care of identifiers oneself - PDO offers no help for this matter.
To make a dynamical identifier safe, one has to follow 2 strict rules:
to format identifiers properly
to verify it against a hardcoded whitelist.
To format an identifier, one has to apply these 2 rules:
Enclose the identifier in backticks.
Escape backticks inside by doubling them.
After such formatting, it is safe to insert the $table variable into query. So, the code would be:
$field = "`".str_replace("`","``",$field)."`";
$sql = "SELECT * FROM t ORDER BY $field";
However, although such formatting would be enough for the cases like ORDER BY, for most other cases there is a possibility for a different sort of injection: letting a user choose a table or a field they can see, we may reveal some sensitive information, like a password or other personal data. So, it's always better to check dynamical identifiers against a list of allowed values. Here is a brief example:
$allowed = array("name","price","qty");
$key = array_search($_GET['field'], $allowed);
$field = $allowed[$key];
$query = "SELECT $field FROM t"; //value is safe
For keywords, the rules are same, but of course there is no formatting available - thus, only whitelisting is possible and ought to be used:
$dir = $_GET['dir'] == 'DESC' ? 'DESC' : 'ASC';
$sql = "SELECT * FROM t ORDER BY field $dir"; //value is safe

Categories