У меня проблема с параметром вопросительного знака в подготовленном заявлении с использованием PDO. Класс My Query выглядит так ( на данный момент я все еще добавляю такие функции, как ограничения данных, фильтрацию настраиваемых параметров и автоматическое обнаружение поддерживаемых операторов для используемого драйвера ):
// SQL query class Query { public $attributes; // constructor for this object public function __construct() { if ($arguments = func_get_args()) { $tmp = explode(" ", current($arguments)); if (in_array(mb_strtoupper(current($tmp)), ["ALTER", "DELETE", "DROP", "INSERT", "SELECT", "TRUNCATE", "UPDATE"], true)) { // classify the query type $this->attributes["type"] = mb_strtoupper(current($tmp)); // get the query string $this->attributes["query"] = current($arguments); // get the query parameters if (sizeof($arguments) > 1) { $this->attributes["parameters"] = array_map(function ($input) { return (is_array($input) ? implode(",", $input) : $input); }, array_slice($arguments, 1, sizeof($arguments))); } return $this; } } } }
Это фрагмент кода, который выполняет запрос:
$parameters = (!empty($this->attributes["queries"][$query]->attributes["parameters"]) ? $this->attributes["queries"][$query]->attributes["parameters"] : null); if ($query = $this->attributes["link"]->prepare($this->attributes["queries"][$query]->attributes["query"], [\PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY])) { if ($query->execute((!empty($parameters) ? $parameters : null))) { return $query->fetchAll(\PDO::FETCH_ASSOC); } }
И так я называю это в своем тестовом коде:
$c1->addQuery("lists/product-range", "SELECT * FROM `oc_product` WHERE `product_id` IN (?);", [28, 29, 30, 46, 47]); if ($products = $c1->execute("test2")) { foreach ($products as $product) { print_r($product); } }
Проблема в том, что я просто вижу первый продукт (это тест против установки OpenCart с помощью ванили) с идентификатором 28. Как вы можете видеть в моем коде, если переданный параметр является массивом, он автоматически обнаруживается лямбдой, которую я имею на месте в конструкторе класса Query, поэтому он отображается в виде строки, такой как 28,29,30,46,47
.
Есть ли отсутствующий параметр в настройке PDO, который мне не хватает? Или, может быть, есть какая-то ошибка или лимит платформы в том, что я делаю? Я знаю, что есть некоторые ограничения на то, что PDO может сделать в отношении массивов, и именно поэтому я предварительно взломал все массивы для их передачи как простую строку.
Есть несколько процедур, которые я видел здесь в SO, который, в основном, составляет строку запроса, например WHERE product_id IN ({$marks})
, где $marks
динамически генерируется с использованием процедуры, такой как str_repeat("?", sizeof($parameters))
но это не то, что я ищу (я мог бы прибегнуть к этому, если нет известной альтернативы, но это не похоже на очень элегантное решение).
Моя среда разработки состоит из: Windows 7 x64, PHP 5.4.13 (x86, потокобезопасная), Apache 2.4.4 (x86) и MySQL 5.6.10 x64.
Любой намек был бы весьма признателен 🙂
A ?
Заполнитель может заменить только один литерал. Если вы хотите, чтобы предложение IN
принимало произвольное количество значений, вы должны подготовить новый запрос для каждой возможной длины вашего массива.
Например, если вы хотите выбрать идентификаторы в массиве [1, 2]
, вам нужен запрос, который выглядит как SELECT * FROM tbl WHERE id IN (?,?)
. Если вы затем передаете массив из трех элементов, вам нужно подготовить запрос типа SELECT * FROM tbl WHERE id IN (?,?,?)
И т. Д.
Другими словами, вы не можете с уверенностью знать, какой запрос вы хотите создать / создать до момента, когда у вас есть данные, которые вы хотите привязать к подготовленному оператору.
Это не ограничение PDO, важно, как подготовленные запросы работают в SQL-базах данных. Подумайте об этом – какой тип данных будет ?
быть в SQL-суше, если вы сказали IN ?
но есть ?
встать на что-то нескалярное?
Некоторые базы данных имеют типы массивов (например, PostgreSQL). Возможно, они могут интерпретировать IN <array-type>
же, как IN (?,?,...)
и это сработает. Но PDO не имеет возможности отправлять или получать данные типа массива (нет PDO::PARAM_ARRAY
), и поскольку это необычная и эзотерическая функция, вряд ли PDO когда-либо будет.
Обратите внимание, что здесь есть дополнительный слой разбитости. Обычная база данных, столкнувшись с условием int_id = '1,2,3,4'
, не будет соответствовать никому, поскольку '1,2,3,4'
не может быть принудительно применено к целому. MySQL, однако, преобразует это в целое число 1
! Вот почему ваш запрос:
$pstmt = $db->prepare('SELECT * FROM `oc_product` WHERE `product_id` IN (?)'); $pstmt->execute(array('28,29,30,46,47'));
Будет соответствовать product_id = 28
. Вот безумие:
mysql> SELECT CAST('28,29,30,46,47' AS SIGNED INTEGER); +------------------------------------------+ | CAST('28,29,30,46,47' AS SIGNED INTEGER) | +------------------------------------------+ | 28 | +------------------------------------------+ 1 rows in set (0.02 sec)
Lambda обнаруживает массив и создает из него строку с комой, а переданный аргумент обрабатывается как строка, поэтому запрос выглядит так:
SELECT * FROM tbl WHERE id IN('1,2,3,4')
'1,2,3,4'
– это одно строковое значение для SQL.
Если вы ожидаете только числовые значения, вы можете опустить их как параметры и просто поместить их в запрос:
$a = [28, 29, 30, 46, 47]; $s = "SELECT * FROM tbl WHERE id IN(".implode(',', array_map('intval', $a)).")";
Для разных типов данных вам необходимо добавить столько же заполнителей параметров, сколько вам нужно, и привязать каждый параметр отдельно.