Placeholder Limits

Placeholders That Do Not Work: LIKE, IN and Identifiers

A placeholder stands for exactly one complete value. In LIKE ? the whole pattern is that value, so the % wildcards go into the bound string, and a visitor's own % and _ must be escaped or they act as wildcards too (Subsection 3.7.3). IN (?) binds one value however many you pass, so generate one ? per element:

Escaping a LIKE pattern and expanding INPHP
<?php
$pdo = require 'db.php';
$st = $pdo->prepare('SELECT COUNT(*) FROM products WHERE title LIKE ?');
$term = '_';                                   // the visitor searched for an underscore
foreach ([$term, addcslashes($term, '%_\\')] as $value) {
  $st->execute(['%' . $value . '%']);
  echo "LIKE '%$value%': ", $st->fetchColumn(), " rows\n";
}
$ids = [2, 5, 7];
$marks = implode(',', array_fill(0, count($ids), '?'));
$st = $pdo->prepare("SELECT sku FROM products WHERE id IN ($marks)");
$st->execute($ids);
echo "IN ($marks): ", implode(' ', $st->fetchAll(PDO::FETCH_COLUMN)), "\n";
Output
LIKE '%_%': 8 rows
LIKE '%\_%': 0 rows
IN (?,?,?): BK-SQL-01 BK-UX-01 AC-MUG-01

Unescaped, the lone underscore matched every title. $marks may be spliced into the SQL because your code wrote every character of it; return early for an empty array, which would give IN (). Table names, column names and keywords such as DESC cannot be bound at all: map the request onto strings you wrote, as in match ($_GET['sort'] ?? '') { 'title' => 'title', default => 'price DESC' }, or quote identifiers with backticks as Subsection 4.17.3 shows.