Как применить метод bindValue в предложении LIMIT?

117

Вот снимок моего кода:

$fetchPictures = $PDO->prepare("SELECT * 
    FROM pictures 
    WHERE album = :albumId 
    ORDER BY id ASC 
    LIMIT :skip, :max");

$fetchPictures->bindValue(':albumId', $_GET['albumid'], PDO::PARAM_INT);

if(isset($_GET['skip'])) {
    $fetchPictures->bindValue(':skip', trim($_GET['skip']), PDO::PARAM_INT);    
} else {
    $fetchPictures->bindValue(':skip', 0, PDO::PARAM_INT);  
}

$fetchPictures->bindValue(':max', $max, PDO::PARAM_INT);
$fetchPictures->execute() or die(print_r($fetchPictures->errorInfo()));
$pictures = $fetchPictures->fetchAll(PDO::FETCH_ASSOC);

я получил

У вас есть ошибка в синтаксисе SQL; проверьте руководство, которое соответствует вашей версии сервера MySQL, чтобы найти правильный синтаксис для использования рядом с '15', 15 'в строке 1

Кажется, что PDO добавляет одинарные кавычки к моим переменным в части LIMIT кода SQL. Я посмотрел, я нашел эту ошибку, которая, как мне кажется, связана: http://bugs.php.net/bug.php?id=44639

Это то, на что я смотрю? Эта ошибка обнаружена с апреля 2008 года! Что нам пока что делать?

Мне нужно создать некоторую разбивку на страницы и убедиться, что данные чистые, безопасные для SQL-инъекций, перед отправкой оператора sql.

Натан Х
источник

Ответы:

165

Я помню, как раньше у меня была эта проблема. Преобразуйте значение в целое число, прежде чем передать его функции привязки. Думаю, это решает проблему.

$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
Стивен Карран
источник
37
Спасибо! Но в PHP 5.3 приведенный выше код выдавал ошибку «Неустранимая ошибка: невозможно передать параметр 2 по ссылке». Он не любит приводить туда int. Вместо того (int) trim($_GET['skip']), чтобы попробовать intval(trim($_GET['skip'])).
Уилл Мартин
5
Было бы здорово, если бы кто-нибудь объяснил, почему это так ... с точки зрения дизайна / безопасности (или другой).
Росс
6
Это будет работать, только если включены эмулированные подготовленные операторы . Он потерпит неудачу, если он отключен (и он должен быть отключен!)
Призрак Мадары
4
@Ross Я не могу конкретно ответить на этот вопрос, но могу указать, что LIMIT и OFFSET - это функции, которые были приклеены ПОСЛЕ всего этого безумия PHP / MYSQL / PDO, поразившего схему разработки ... На самом деле, я считаю, что это сам Лердорф наблюдал Внедрение LIMIT несколько лет назад. Нет, это не отвечает на вопрос, но указывает на то, что это надстройка вторичного рынка, и вы знаете, насколько хорошо они иногда могут работать ....
FredTheWebGuy
2
@Ross PDO не допускает привязку к значениям - скорее к переменным. Если вы попробуете bindParam (': something', 2), у вас будет ошибка, поскольку PDO использует указатель на переменную, которой не может быть число (если $ i равно 2, у вас может быть указатель на $ i, но не на номер 2).
Kristijan
44

Самым простым решением было бы выключить режим эмуляции. Вы можете сделать это, просто добавив следующую строку

$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );

Также этот режим может быть установлен как параметр конструктора при создании соединения PDO . Это могло быть лучшим решением, поскольку некоторые сообщают, что их драйвер не поддерживает эту setAttribute()функцию.

Это не только решит вашу проблему с привязкой, но также позволит вам отправлять значения напрямую execute(), что значительно сделает ваш код короче. Если предположить, что режим эмуляции уже установлен, все это займет около полдюжины строк кода.

$skip = isset($_GET['skip']) ? (int)trim($_GET['skip']) : 0;
$sql  = "SELECT * FROM pictures WHERE album = ? ORDER BY id LIMIT ?, ?";
$stmt  = $PDO->prepare($sql);
$stmt->execute([$_GET['albumid'], $skip, $max]);
$pictures = $stmt->fetchAll(PDO::FETCH_ASSOC);
Ваш здравый смысл
источник
SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes... Почему для меня это никогда не бывает так просто :) Хотя я уверен, что это поможет большинству людей, в моем случае мне пришлось использовать что-то похожее на принятый ответ. Внимание будущим читателям!
Мэтью Джонсон
@MatthewJohnson, что это за драйвер?
Ваш здравый смысл
Не уверен, но в инструкции написано PDO::ATTR_EMULATE_PREPARES Enables or disables emulation of prepared statements. Some drivers do not support native prepared statements or have limited support for them. Для меня это ново, но опять же, я только начинаю работать с PDO. Обычно использую mysqli, но решил, что попытаюсь расширить свой кругозор.
Мэтью Джонсон,
@MatthewJohnson, если вы используете PDO для mysql, драйвер действительно поддерживает эту функцию. Итак, вы получили это сообщение из-за какой-то ошибки
Your Common Sense
1
Если вы получили сообщение о проблеме с поддержкой драйверов, проверьте еще раз, вызываете ли вы setAttributeинструкцию ($ stm, $ stmt) не для объекта pdo.
Jehong Ahn
17

Глядя на отчет об ошибке, может сработать следующее:

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);

$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);  

но уверены ли вы, что ваши входящие данные верны? Поскольку в сообщении об ошибке, кажется, есть только одна кавычка после числа (в отличие от всего числа, заключенного в кавычки). Это также может быть ошибкой ваших входящих данных. Вы можете сделать это, print_r($_GET);чтобы узнать?

Пекка
источник
1
'15', 15 '. Первое число полностью заключено в кавычки. Второе число вообще не имеет кавычек. Так что да, данные хорошие.
Nathan H
8

Это просто резюме.
Есть четыре варианта параметризации значений LIMIT / OFFSET:

  1. Отключите, PDO::ATTR_EMULATE_PREPARESкак указано выше .

    Это не позволяет значениям, передаваемым per, ->execute([...])всегда отображаться в виде строк.

  2. Переключитесь на ручное заполнение ->bindValue(..., ..., PDO::PARAM_INT)параметров.

    Что, однако, менее удобно, чем -> выполнить список [].

  3. Просто сделайте здесь исключение и просто интерполируйте простые целые числа при подготовке SQL-запроса.

     $limit = intval($limit);
     $s = $pdo->prepare("SELECT * FROM tbl LIMIT {$limit}");

    Кастинг важен. Чаще всего ->prepare(sprintf("SELECT ... LIMIT %d", $num))используется для таких целей.

  4. Если вы не используете MySQL, но, например, SQLite или Postgres; вы также можете приводить связанные параметры непосредственно в SQL.

     SELECT * FROM tbl LIMIT (1 * :limit)

    Опять же, MySQL / MariaDB не поддерживает выражения в предложении LIMIT. Еще нет.

марио
источник
1
Я бы использовал sprintf () с% d для 3, я бы сказал, что это немного более стабильно, чем с переменной.
hakre
Да, varfunc cast + интерполяция - не самый практичный пример. Я часто использую свою лень {$_GET->int["limit"]}для таких случаев.
Марио
7

для LIMIT :init, :end

Вам нужно так привязать. если у вас было что-то подобное, $req->execute(Array());это не сработает, так как оно будет PDO::PARAM_STRприменяться ко всем варам в массиве, и LIMITвам абсолютно необходимо целое число. bindValue или BindParam, как хотите.

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
Николя Манзини
источник
2

Поскольку никто не объяснил, почему это происходит, я добавляю ответ. Причина этого в том, что вы используете trim(). Если вы посмотрите руководство по PHP для trim, тип возврата - string. Затем вы пытаетесь передать это как PDO::PARAM_INT. Вот несколько способов обойти это:

  1. Используйте, filter_var($integer, FILTER_VALIDATE_NUMBER_INT)чтобы убедиться, что вы передаете целое число.
  2. Как говорили другие, используя intval()
  3. Кастинг с (int)
  4. Проверка, является ли это целым числом с is_int()

Есть еще много способов, но это основная причина.

Мелисса Уильямс
источник
3
Это происходит даже тогда, когда переменная всегда была целым числом.
felwithe
1

bindValue смещение и ограничение с использованием PDO :: PARAM_INT, и он будет работать

Карел
источник
-1

// ДО (Текущая ошибка) $ query = ".... LIMIT: p1, 30;"; ... $ stmt-> bindParam (': p1', $ limiteInferior);

// ПОСЛЕ (ошибка исправлена) $ query = ".... LIMIT: p1, 30;"; ... $ limiteInferior = (int) $ limiteInferior; $ stmt-> bindParam (': p1', $ limiteInferior, PDO :: PARAM_INT);

Брайан Хосуэ Медина Мелендес
источник
-1

PDO::ATTR_EMULATE_PREPARES дал мне

Драйвер не поддерживает эту функцию: этот драйвер не поддерживает ошибку настройки атрибутов.

Мое обходное решение заключалось в том, чтобы задать $limitпеременную как строку, а затем объединить ее в операторе подготовки, как в следующем примере:

$limit = ' LIMIT ' . $from . ', ' . $max_results;
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE company_id = :cid ORDER BY name ASC' . $limit . ';' );
try {
    $stmt->execute( array( ':cid' => $company_id ) );
    ...
}
catch ( Exception $e ) {
    ...
}
Ребра
источник
-1

Многое происходит между различными версиями PHP и странностями PDO. Я попробовал 3 или 4 метода здесь, но не смог заставить работать LIMIT.
Я предлагаю использовать форматирование / объединение строк с фильтром intval () :

$sql = 'SELECT * FROM `table` LIMIT ' . intval($limitstart) . ' , ' . intval($num).';';

Очень важно использовать intval () для предотвращения SQL-инъекции, особенно если вы получаете свой лимит от $ _GET и т.п. Если вы сделаете это, это будет самый простой способ заставить LIMIT работать.

Существует много разговоров о «проблеме с LIMIT в PDO», но я считаю, что параметры PDO никогда не должны использоваться для LIMIT, поскольку они всегда будут целыми числами, и работает быстрый фильтр. Тем не менее, это немного вводит в заблуждение, поскольку философия всегда заключалась в том, чтобы не выполнять фильтрацию SQL-инъекций самостоятельно, а скорее «Попросить PDO обработать это».

Tycon
источник