PDO работа с :placeholders через bindValue

membrilius

Новичок
Добрый день!

Работаю с placeholders используя bindValue. Т.к. в таком случае я могу их использовать в некоторых недоступных местах для методов prepare()+execute($arrayData). Например использовать placeholders в конструкции LIMIT.

Столкнулся с некоторой особенностью.

PHP:
$STH = $DBH->prepare('SELECT * FROM users WHERE user_start_time <= :time AND user_end_time > :time');

$STH->bindValue('time', 1348372943);

$STH->execute();
При использовании двух одинаковых placeholders выдаст ошибку несоблюдения количества placeholders с переданными путем bindValue.

То есть вот так будет работать:

PHP:
$STH = $DBH->prepare('SELECT * FROM users WHERE user_start_time <= :time_1 AND user_end_time > :time_2');

$STH->bindValue('time_1', 1348372943);
$STH->bindValue('time_2', 1348372943);

$STH->execute();
Это PDO такой кривой, или я что-то не так делаю?

Например prepare + execute может быть несколько одинаковых placeholders, но тогда я упираюсь в невозможность использовать их в такой нужной конструкции как LIMIT.
 

membrilius

Новичок
Я уже пригуглился к этой теме - http://phpclub.ru/talk/threads/Привязка-одного-и-того-же-значения-к-нескольким-плейсхолдерам-тезкам.76410/

И что-то как-то не радостно. Если PDO вдруг подключит драйвер ещё более убогой СУБД, то возможностей видимо станет ещё меньше.

Не понятно... я как разработчик продукта выбираю СУБД и представляю какие в них различия следовательно их можно вынести на уровень драйвера, а не на уровень всего PDO
 

membrilius

Новичок
PDO::ATTR_EMULATE_PREPARES работает только если указывать четко передаваемый тип в bindValue

Не совсем удобно его определять. Это было бы может и удобно если PDO пытался конвертировать передаваемое значение в указанный тип. Но он этого не делает.

PHP:
$STH->bindValue(':lim' ,'10', PDO::PARAM_INT); // Error!
 

membrilius

Новичок
Воспользуюсь идеей hell0w0rd, буду к заменять плейсхолдеры. Только они будут именованные с префиксом.
 

hell0w0rd

Продвинутый новичок
membrilius
смысл?) все равно прийдется хранить карту соответствия)
 

Фанат

oncle terrible
Команда форума
PDO::ATTR_EMULATE_PREPARES работает ВСЕГДА. Ничего никуда указывать не нужно.
С лимитом действительно засада.
Однако учитывая, что параметры лимита обычно всегда вычисляются, то ты первый, для кого он стал камнем преткновения.
 

membrilius

Новичок
hell0w0rd
Мне так просто нагляднее отлаживать запрос, не нужно искать позицию вставляемого значения.

Фанат

PDO::ATTR_EMULATE_PREPARES = true можно использовать одинаковые имена плейсхолдеров, но если я хочу задать лимит или другое, где не нужно заключать значение в кавычки, придется указать тип.

Лимит нужен. Т.к удобнее отдать значения лимитов с другими данными массивом, чем пихать руками в строку запроса. Органично же ведь.
 

Фанат

oncle terrible
Команда форума
Вообще, я извиняюсь за несколько резковатый тон моих реплик. Это вылезает раздражение на себя самого за несообразительность.
В кои-то веки нормальный вопрос про ПДО.
А его работа с плейсхолдерами действительно вызывает много нареканий.

Теоретически, конечно, можно было бы сделать враппер, который определяет тип переменной, и подставляет нужное значение в bindValue - и это будет работать в 99% случаев. Но этот оставшийся 1% - неаккуратненько.

Поэтому да - самая продуктивная, имхо, идея - это типизованные плейсхолдеры. Таким образом мы убиваем сразу двух зайцев - избавляемся от необходимости писать тип в bindValue (что позволяет нам автоматизировать процесс биндинга) и при этом никогда не иметь проблем с типами.

Вот только последний ваш обмен репликами я не понял.
Воспользуюсь идеей hell0w0rd, буду к заменять плейсхолдеры. Только они будут именованные с префиксом.
Ну так у него как раз и есть - именованные с префиксом. Префикс определяет тип.
Я, видимо, контекст потерял. просветите, если не в лом.
membrilius
смысл?) все равно придется хранить карту соответствия)
И, соотсветственно, эту реплику тоже.
 

membrilius

Новичок
Фанат

У hell0w0rd запрос пишется с именованными плейсхолдерами с возможностью указания типа. Далее они заменяются на не именованные. Составляется карта замен и "биндятся" с указанием типа или без.

Если плейсхолдеры не именованные, то карта замен будет с числовыми индексами. Это хорошо если полученный запрос не нужно отлаживать.

Например было так:

PHP:
$sql = "SELECT * FROM users WHERE user_name=:name AND user_name=:name LIMIT :start, :step";

$DBData = array();
$DBData['name'] = 'Jack';
$DBData['start'] = '0';
$DBData['spep'] = '15';
Будет так:

PHP:
$sql = "SELECT * FROM users WHERE user_name=? AND user_name=? LIMIT ?, ?";

// Карта соответствия
$DBData = array();
$DBData[1] = 'Jack';
$DBData[2] = 'Jack';
$DBData[3] = '0';
$DBData[4] = '15';
Я заменяю так:

PHP:
$sql = "SELECT * FROM users WHERE user_name=:name__1 AND user_name=:name__2 LIMIT :start__3, :step__4";

// Карта соответствия
$DBData = array();
$DBData['name__1'] = 'Jack';
$DBData['name__2'] = 'Jack';
$DBData['start__3'] = '0';
$DBData['step__4'] = '15';
Лично мне на громоздкие запросы смотреть так удобнее.
 

Фанат

oncle terrible
Команда форума
Спасибо.
Но для отладки же удобнее всего запрос совсем распарсить, подставив сразу значения - не?
стаковерфлой ломится от вопросов "как получить нормальный SQL из плейсхолдеров".
 
Последнее редактирование:

hell0w0rd

Продвинутый новичок
Карта соответствия не такая.
Мы храним массив номер => плейсхолдер и плейсхолдер => тип
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
для отладки же удобнее всего запрос совсем распарсить, подставив сразу значения - не?
стаковерфлой ломится от вопросов "как получить нормальный SQL из плейсхолдеров".
а зачем это вообще нужно - приводить типизированные плейсхолдеры к нетипизированным неименованным?
чем это лучше корректной подготовки данных и слияния всего запроса в строку? зачем добавлять абстракцию вместо того чтобы заменить не очень удачную реализацию собственной?
 

riff

Новичок
Извините, что несколько не по теме. Я знаю, что "тебя не об этом спрашивают", но просто темы близки...

Очень удобную штуку написал Фанат. Пользуясь случаем, Фанат, спасибо ещё раз.
Готовым решением я, правда, не смог воспользоваться: встроил в свой класс для работы с database, ну и чуть допилил по своему (если почитаешь тему, то будет понятно что именно).
Выложил код сюда http://pastebin.com/gppkjMDM. Всё можешь не смотреть, только "function sql".
Теперь, чтобы сделать, например, выборку мне достаточно написать:
PHP:
$row = DB::query('SELECT ... WHERE name = ?', array('Вася'))->get();
или $row = DB::query('SELECT ... WHERE id = ?i', array(55))->get();
или $rows = DB::query('SELECT ... WHERE id > ?i:min AND id < ?i:max', 
          array('min'=>3, 'max'=>10))->getAll();
ну или, в твоём случае $rows = DB::query('
           SELECT * FROM users 
           WHERE user_start_time <= ?i:time AND user_end_time > ?i:time 
           LIMIT ?i:limit', array('time'=>1348372943, 'limit'=>5)->getAll();
 
Последнее редактирование:

Фанат

oncle terrible
Команда форума
Очень удобную штуку написал Фанат. Пользуясь случаем, Фанат, спасибо ещё раз.
Спасибо за отзыв.
Обязательно посмотрю.
Статик вариант надо делать 100%, а вот в method chaining не вижу большого смысла.
Все-таки, половина запросов тут далеко за экран уехала - что, на мой вкус, не дело.

О! Только дошло, что здесь именованные плейсхолдеры, которые я только-только собираюсь делать.

Возвращаясь к теме топика, Фанат, по мотивам обсуждения здесь, нарисовал то же самое для PDO :)
https://github.com/colshrapnel/pdohelper
Учтя как раз те косяки, о которых здесь говорилось:
PHP:
$sql = 'SELECT * FROM users WHERE user_start_time <= :time AND user_end_time > :time LIMIT :limit';
$rows = $db->run($sql, ['time'=>1348372943, 'limit'=>5], "all");
 

hell0w0rd

Продвинутый новичок
PHP:
if (is_int($value) || ctype_digit($value))
{
     $value = intval($value);
     $type  = PDO::PARAM_INT;                       
}
В чем смысл этого кода?
И вообще в чем смысл биндить значения на основе их типов?
 
Сверху