I'm working on a WordPress plugin and keep getting a PHPCS warning that I can't seem to resolve. The warning is:
WARNING WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
Incorrect number of replacements passed to $wpdb->prepare(). Found 3 replacement parameters, expected 2.
Here's my code snippet:
// Check for image URL or filename in postmeta
if ($attachment_url) {
$attachment_basename = basename($attachment_url);
// Prepare the status placeholders
$placeholders = implode(',', array_fill(0, count($statuses), '%s'));
$meta_url_usage = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*)
FROM $wpdb->postmeta pm
JOIN $wpdb->posts p ON p.ID = pm.post_id
WHERE (pm.meta_value LIKE %s OR pm.meta_value LIKE %s)
AND p.post_status IN ($placeholders)",
'%' . $wpdb->esc_like($attachment_url) . '%',
'%' . $wpdb->esc_like($attachment_basename) . '%',
...$statuses
)
);
if ($meta_url_usage > 0) {
return true;
}
What I've tried:
Problem is PHPCS thinks I should only have 2 replacements (for the LIKE clauses). But I need to pass the status values for the IN clause. The spread operator (...$statuses) is adding additional parameters.
My Question: Is this a false positive in PHPCS or am I doing something wrong? What's the proper way to handle dynamic IN clauses with $wpdb->prepare()? Or, should I suppress the Warning?
The code works as expected, but I want to make sure I'm following WordPress coding standards properly. Any insights would be greatly appreciated!
To keep your code working AND clean for PHPCS, restructure it slightly. WordPress coding standards suggest building the full list of placeholders and arguments first, then passing them as a single array.
$like_attachment_url = '%' . $wpdb->esc_like($attachment_url) . '%';
$like_attachment_basename = '%' . $wpdb->esc_like($attachment_basename) . '%';
// Prepare the status placeholders and full list of replacements
$placeholders = implode(',', array_fill(0, count($statuses), '%s'));
$sql = "
SELECT COUNT(*)
FROM $wpdb->postmeta pm
JOIN $wpdb->posts p ON p.ID = pm.post_id
WHERE (pm.meta_value LIKE %s OR pm.meta_value LIKE %s)
AND p.post_status IN ($placeholders)
";
$args = array_merge([$like_attachment_url, $like_attachment_basename], $statuses);
$meta_url_usage = $wpdb->get_var($wpdb->prepare($sql, $args));
NB: You do not need to suppress the warning if you use this pattern.