Passing values to MySQL IN operation in PDO prepared statement? - php

I have a form field that is returning a comma-delimited string that I want to pass in to a PHP PDO MySQL query IN operation, but the IN operation requires that the values be comma-delimited (as opposed to my string of delimited values).
How do I do this?
$values = $_POST['values']; # '10,5,4,3' (string)
$query = "SELECT * FROM table WHERE id IN (:values)";
$data = array( ':values' => $values );

You can't pass in multiple values in a single placeholder. You will have to enter a different placeholder for each value to be passed into IN (). Since you don't know how many there will be, use ? instead of named parameters.
$values = explode(',', $values) ;
$placeholders = rtrim(str_repeat('?, ', count($values)), ', ') ;
$query = "SELECT * FROM table WHERE id IN ($placeholders)";
$stm = $db->prepare($query) ;
$stm->execute($values) ;

PDO alone cannot bind arrays to a :parameter. You need a helper function for that.
Also in your example, the literal string '10,5,4,3' would be received as one value. Parameter binding will turn it into .. id IN ('10,5,4,3') and not into a list comparison.
The workaround in your case would be to fall back on using dynamic SQL and escaping.
$values = preg_replace('/[^\d,]/', "", $_POST['values']);
$query = "SELECT * FROM table WHERE id IN ($values)";
I'm personally using a wrapper/helper function which has a special syntax for arrays (but you don't actually have one to begin with, so it would be a double workaround):
db("SELECT * FROM table WHERE id IN (??)", explode(",",$values));

The trick is to recognize that $values is a bunch of individual values, and set up your query with this in mind. This is easier to do if you use ? placeholders instead of named placeholders. For example, you could do something like the following:
$values = explode(',', $_POST['values']); //array(10,5,4,3)
$placeholder_string = implode(',', array_fill(0, count($values), '?')); // string '?,?,?,?'
$query = "SELECT * FROM table WHERE id IN ($placeholder_string)";
$statement = $db->prepare($query);
$statement->execute($values);

Related

PHP string replace to prepare PDO SQL

I have some SQL queries that use PHP string variables to create the query before PDO prepare().
$connection = new PDO(...);
// Make variable placeholder for each column.
$params = array();
foreach ($row as $col => $value) {
$params[':' . $col] = $value;
}
$columns = implode(', ', array_keys($row));
$values = implode(', ', array_keys($params));
$query = "
INSERT INTO my_table ($columns)
VALUES ($values)
";
$statement = $connection->prepare($query);
$statement->execute($params);
Or something similar with SELECT:
$query = "
SELECT field
FROM my_table
WHERE id IN ($ids)
";
Where the query will become
$query = "
SELECT field
FROM my_table
WHERE id IN (:id0, :id1, :id2)
";
and then the execute() function will pass in the params like array(':id0' => 0, ...).
Is this vulnerable to injection if the part being inserted is just a bunch of placeholders to be used for query preparation? And is there a better way to do this in PHP with PDO?
When binding a dynamic number of parameters, I revert to the ? placeholders. You can do:
$placeholders = implode(',', array_fill(0, count($values), '?'));
$query = "SELECT field FROM my_table WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($query);
$stmt->execute($values);
You still need to do string substitution if the column names are dynamic, as in your INSERT example. Those should be white-listed to prevent injection. But you can use the above mechanism for the values being inserted. You'll need to use
$values = array_values($params);
because ? placeholders can't be filled in from an associative array.

Prepared statement WHERE IN clause behaving unexpected [duplicate]

This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 1 year ago.
I'm reworking some PHP code to use PDO for the database access, but I'm running into a problem with a "WHERE... IN" query.
I'm trying to delete some things from a database, based on which items on a form are checked. The length and content of the list will vary, but for this example, imagine that it's this:
$idlist = '260,201,221,216,217,169,210,212,213';
Then the query looks like this:
$query = "DELETE from `foo` WHERE `id` IN (:idlist)";
$st = $db->prepare($query);
$st->execute(array(':idlist' => $idlist));
When I do this, only the first ID is deleted. (I assume it throws out the comma and everything after it.)
I've also tried making $idlist an array, but then it doesn't delete anything.
What's the proper way to use a list of items in a PDO prepared statement?
Since you can't mix Values (the Numbers) with control flow logic (the commas) with prepared statements you need one placeholder per Value.
$idlist = array('260','201','221','216','217','169','210','212','213');
$questionmarks = str_repeat("?,", count($idlist)-1) . "?";
$stmt = $dbh->prepare("DELETE FROM `foo` WHERE `id` IN ($questionmarks)");
and loop to bind the parameters.
This may be helpful too:
https://phpdelusions.net/pdo#in
$arr = [1,2,3];
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE column IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($arr);
$data = $stm->fetchAll();
I would make $idlist and array, then simply loop through the array using foreach to delete the specific item.
$idlist = array('260','201','221','216','217','169','210','212','213');
$stmt = $dbh->prepare("DELETE FROM `foo` WHERE `id` = ?");
$stmt->bindParam(1, $id);
foreach ($idlist as $item){
$id = $item;
$stmt->execute();
}

Doctrine - How to bind array to the SQL?

My SQL looks something like this:
$sql = "select * from user where id in (:userId) and status = :status";
$em = $this->getEntityManager();
$stmt = $em->getConnection()->prepare($sql);
$stmt->bindValue(':userId', $accounts, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
$stmt->bindValue(':status', 'declined');
$stmt->execute();
$result = $stmt->fetchAll();
But it returns:
An exception occurred while executing (...)
with params
[[1,2,3,4,5,6,7,8,11,12,13,14], "declined"]
Notice: Array to string conversion
I cannot user queryBuilder because my real SQL is more complicated (ex. contains joined select, unions and so on)
You can't use prepared statements with arrays simply because sql itself does not support arrays. Which is a real shame. Somewhere along the line you actually need to determine if your data contains say three items and emit a IN (?,?,?). The Doctrine ORM entity manager does this for you automatically.
Fortunately, the DBAL has you covered. You just don't use bind or prepare. The manual has an example: https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion
In your case it would look something like:
$sql = "select * from user where id in (?) and status = ?";
$values = [$accounts,'declined'];
$types = [Connection::PARAM_INT_ARRAY, \PDO::PARAM_STR];
$stmt = $conn->executeQuery($sql,$values,$types);
$result = $stmt->fetchAll();
The above code is untested but you should get the idea. (Make sure you use Doctrine\DBAL\Connection; for Connection::PARAM_INT_ARRAY)
Note for people using named parameters:
If you are using named parameters (:param instead of ?), you should respect the parameter names when providing types. For example:
$sql = "select * from user where id in (:accounts) and status = :status";
$values = ['accounts' => $accounts, 'status' => 'declined'];
$types = ['accounts' => Connection::PARAM_INT_ARRAY, 'status' => \PDO::PARAM_STR];
If you want to stick to the :param syntax where order does not matter, you have to do a bit of extra work, but I'll show you an easier way to bind the parameters:
// store all your parameters in one array
$params = array(
':status' => 'declined'
);
// then, using your arbitrary array of id's ...
$array_of_ids = array(5, 6, 12, 14);
// ... we're going to build an array of corresponding parameter names
$id_params = array();
foreach ($array_of_ids as $i => $id) {
// generate a unique name for this parameter
$name = ":id_$i"; // ":id_0", ":id_1", etc.
// set the value
$params[$name] = $id;
// and keep track of the name
$id_params[] = $name;
}
// next prepare the parameter names for placement in the query string
$id_params = implode(',', $id_params); // ":id_0,:id_1,..."
$sql = "select * from user where id in ($id_params) and status = :status";
In this case we end up with:
"select * from user where id in (:id_0,:id_1,:id_2,:id_3) and status = :status"
// now prepare your statement like before...
$stmt = $em->getConnection()->prepare($sql);
// ...bind all the params in one go...
$stmt->execute($params);
// ...and get your results!
$result = $stmt->fetchAll();
This approach will also work with an array of strings.
You need to wrap them in an array
$stmt->bindValue(':userId', array($accounts), array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY));
http://doctrine-dbal.readthedocs.io/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion
edit
I should have elaborated more. You cannot bind an array like that, dont prepare the sql execute directly as the example in the docs.
$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
array(array(1, 2, 3, 4, 5, 6)),
array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY));
You cannot bind an array of values into a single prepared statement parameter

Binding issues of slash with quotes

I am using php mysql-pdo and symfony
issue is : I have a query as stated below
WHERE STATUS IN (:STATUS)
with value of status as A','B.
and when i bind this as sting its escaping as below
WHERE STATUS IN ('A\',\'B')
and hence wrong output.
pleass help
Prepared statement can represent a complete data literal only. Not a part of literal, nor a complex expression, nor identifier. But either string or number only. Thus, your query doesn't work as you are actually binding a complex expression, not because of quotes
One have to create a query with placeholders representing every array member, and then bind this array values for execution:
$ids = array(1,2,3);
$stm = $pdo->prepare("SELECT * FROM t WHERE id IN (?,?,?)");
$stm->execute($ids);
To make this query more flexible, it's better to create a string with ?s dynamically:
$ids = array(1,2,3);
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE column IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($ids);
$data = $stm->fetchAll();
Of course, if we have other variables to be bound, we need to add them to values array:
$ids = array(1,2,3);
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE column IN ($in) AND category=?";
$stm = $db->prepare($sql);
$ids[] = $category; //adding another member to array
$stm->execute($ids);
$data = $stm->fetchAll();
the code become quite bloated but that's all PDO can offer to handle such complex cases. As a further improvement one can invent their own placeholders to support such complex data types.

Pull Array Value matches from a SQL Database with PDO (PHP)

I have an array of ids $friends = array(0001, 0002, 0003, 0004) and a database where table_name = friends, column_header = fid. fid in friends may or may not contain one of the friend IDs. I want to input $friends into the query, and return all of the present values that were both in $friends and in a row of fid.
I'm sure the fid={array_values($friends)} is wrong, but I don't know how to pass the WHERE portion an array of values...
//All DB_X's are defined in another file that is included in this actual file
$db = new PDO("mysql:host=".DB_SERVER.";dbname=".DB_NAME, DB_USER, DB_PASS);
$stmt = $db->prepare("SELECT fid FROM friends WHERE fid={array_values($friends)} ORDER BY fid ASC");
$stmt->execute();
$friendResults = $stmt->fetchAll();
You will need to make use of SQL's IN operator:
SELECT ... FROM ... WHERE foo IN (val1, val2, ...)
You can use PHP's implode() function to get the desired SQL bit:
$values = implode(', ', array_values($friends));
$query = "SELECT ... FROM ... WHERE fid IN ({$values})";
The above will work if your values are numeric. If they are strings, you'll have to modify the values before implode()ing:
$values = array_map(array_values($friends), function($value) {
return "'{$value}'"; // Here is where you could do sanitization
});
$values = implode(', ', $values);
PLEASE NOTE: You must properly sanitize the data in $friends to prevent SQL injection.

Categories