For the task of executing a secure mysqli query with a dynamic number of incoming values to be fed into the sql string, a prepared statement is the professional technique to implement.
Let's assume that the incoming data payload is user-supplied data -- this means that we cannot guarantee the integrity of the data nor can we guarantee the volume of data. In fact, the expected array of data might be empty. The below snippet will demonstrate how to pass an array of ids to the IN ()
condition in the WHERE
clause of a prepared statement. If there are no values in the array, then a prepared statement provides no benefit and should not be used.
MySQLi result set objects can be immediately iterated by a foreach()
loop. Therefore, it is not necessary to make iterated fetch
calls; just access the rows' data using array syntax.
The array of ids means that the sql will expect integer values. When calling bind_param()
, the first parameter will be a single string of repeated i
characters. For general use, if the data will be strings or you might have a mix of data types (e.g. integers, floats/doubles, or strings), then is simpler to just use repeated s
characters instead of i
characters.
Code: (PHPize.online Demo with SQL)
$ids = [1, 5, 18, 25]; // this could be, for example: $_GET['ids']
$count = count($ids);
$sql = 'SELECT name FROM somewhere';
$orderBy = 'ORDER BY name';
if ($count) {
$placeholders = implode(',', array_fill(0, $count, '?'));
$stmt = $mysqli->prepare("$sql WHERE id IN ($placeholders) $orderBy");
$stmt->bind_param(str_repeat('i', $count), ...$ids);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $mysqli->query("$sql $orderBy"); // a prepared statement is unnecessary
}
foreach ($result as $row) {
echo "<div>{$row['name']}</div>\n";
}
Output from my PHPize demo:
<div>Alan</div>
<div>Bill</div>
<div>Chad</div>
<div>Dave</div>
If you don't need to iterate the result set for any reason, then you can fetch_all()
. This is commonly used when immediately echoing or returning a json-encoded string (say, as the response to an ajax call). In this case, you replace the foreach()
block with: (PHPize.online Demo with SQL)
echo json_encode($result->fetch_all(MYSQLI_ASSOC));
or simply dump the multidimensional array:
var_export($result->fetch_all(MYSQLI_ASSOC));
Output from my PHPize demo:
[{"name":"Alan"},{"name":"Bill"},{"name":"Chad"},{"name":"Dave"}]
From PHP8.1 and higher, it is no longer necessary to call bind_param()
because the execute()
method can receive the payload of parameters as an array (like PDO).
This means that...
$stmt->bind_param(str_repeat('i', $count), ...$ids);
$stmt->execute();
can be replaced with...
$stmt->execute($ids);
Here's a complete, basic example: (PHPize.online Demo)
$ids = [1, 2, 3, 4, 5];
$stmt = $mysqli->prepare("SELECT * FROM somewhere WHERE id IN (" . rtrim(str_repeat('?,', count($ids)), ',') . ") ORDER BY id");
$stmt->execute($ids);
var_export($stmt->get_result()->fetch_all(MYSQLI_ASSOC));
Topical Resources: