The requirement
Imagine that you have an HTML form with a number of rows and checkboxes, and your server is to produce some output based on the selected rows.
<form action="processRows.php" method="post">
<table>
<tbody>
<tr><td>Row One</td><td><input type="checkbox" value="1" name="selected[]"></td></tr>
<tr><td>Row Two</td><td><input type="checkbox" value="2" name="selected[]"></td></tr>
<tr><td>Row Three</td><td><input type="checkbox" value="3" name="selected[]"></td></tr>
<tr><td>Row Four</td><td><input type="checkbox" value="4" name="selected[]"></td></tr>
</tbody>
</table>
<input type="submit" name="submit">
</form>
When the form is submitted to your PHP program, your code will see an array in $_POST['selected']
that contains all the values of the checked checkboxes.
Your code has to retrieve all the rows in the table that match the values in the selected[]
array. You could just loop through the array and retrieve the rows one at a time but that's horribly inefficient. What you need is a query like SELECT * FROM myTable WHERE `IDX` in ('1','2','4')
, but how to do that safely?
The contents of $selected[]
are user data and cannot be trusted. The best* way to handle user data is to use a prepared statement, but we don't know how many values we might have to process. We need a dynamic way to produce a statement that we can prepare.
Creating the query
The basic form of statement we need is
SELECT * FROM myTable WHERE `IDX` in (?,?,?)
We know how many values we have to process from the size of $_POST['selected']
so we can use that to create a list of placeholders:
// Create a string of the form ?,?,?,?,
$placeholders = str_repeat('?,', count($_POST['selected']);
// strip the trailing comma, and concatenate the parts of the query
$placeholders = trim($placeholders,',');
$query = "SELECT * FROM myTable WHERE `IDX` in (".$placeholders.).")";
// Now prepare the statement. Note that we have not included any user data in the query we're preparing
$stmt = $conn->prepare($query);
Binding the data
For PDO, binding the data is very easy. We could usebindParam()
, but there's an easier way: supply the $_POST['selected']
array as a parameter to the execute()
method:
$stmt->execute($_POST['selected']);
For MySQLi we need to do a little more. We can bind the entire array in one operation, but we need to create the types string and expand the array to match:
$types = str_repeat('s', count($_POST['selected'])); // All $_POST values are strings.
$stmt->bind_param($types, ...$_POST['selected']); // Use the ... spread operator too unpack the array
From this point on, retrieve the data in the usual way.
Complete PDO code
$pdo = new PDO('mysql:dbname=schemaName;host=localhost', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$paramList = str_repeat("?,", count($_POST['selected']));
$query = "select * from myTable where idx in (".trim($paramList, ',').")";
$stmt = $pdo->prepare($query);
$stmt->execute($_POST['selected']);
while ($row = $stmt->fetch()) {
echo $row['result'];
echo "<br>\n";
}
Complete MySQLI code
$mysqli = new mysqli('localhost','user','password','schema');
$paramList = str_repeat("?,", count($_POST['selected']));
$query = "select result from myTable where idx in (".trim($paramList, ',').")";
$stmt = $mysqli->prepare($query);
$types = str_repeat('s', count($_POST['selected'])); // All $_POST values are strings.
$stmt->bind_param($types, ...$_POST['selected']); // Use the ... spread operator to unpack the array
$stmt->execute();
$stmt->bind_result($resultData);
while ($row = $stmt->fetch()) {
echo $resultData;
echo "<br>\n";
}
This is the data used for the table myTable
id | idx | result |
1 | 1 | Result 1 |
2 | 2 | Result 2 |
3 | 3 | Result 3 |
4 | 4 | Result 4 |
5 | 5 | Result 5 |
* What's 'best' can vary. In this case, where all the values are expected to be integers, using a filter can guarantee safety without needing a prepared statement. That's a subject for a different article.
Note: This code was written and tested using PHP8. It should work for some earlier versions of PHP.