Класс для безопасной работы с MySQL

damner2

Новичок
Фанат
Вот тебе ещё несколько идей для развития класса:

1. Имплементировать Serializable интерфейс (чтоб можно было спокойно сериализовывать настроенный экземпляр класса).
2. Коннектиться к БД не сразу, а в момент первого запроса.
3. Прикрутить логирование выполняемых запросов, ну или подумать как его будешь делать. Желательно иметь возможность программно получать список запросов, которые выполнялись, возможно с некоторыми характеристиками типа "время", "кол-во возвращённых строк".

Сможешь занять себе ещё на недельку вместо работы :D
 

serglt

Анус, ой, Ахтунг
Поддерживаю фиксера, плюс чтоб не было войны, плейсхолдеры заменять тока по условию, вызов больше чем с одним параметром заменяем плейсхолдеры, параметр тока один запрос - выполняем как есть.
Вообще проверку сколько плейсхолдеров, и сколько параметров переданно - считаю лишней. Ну выдаст ошибку база query error SELECT * FROM table WHERE x = 1 LIMIT 10,?i - ну понятно ж будет что забыл добавить в запрос.
Плюс возможность вот такого типа не будет лишней
PHP:
$data = $db->getAll("SELECT * FROM table WHERE ?q LIMIT ?i,?i", array ("one = ?s",$one), $start, $per_page);

function parse ($q) {
    $a = func_get_args ();
    if (count ($a) == 1) return $q;
    return $this->prepareQuery ($a);
}
    case '?q':
        if (is_array ($value)) call_user_func_array (array ($this, 'prepareQuery'), $value);
        break;
 

Фанат

oncle terrible
Команда форума
1. Имплементировать Serializable интерфейс (чтоб можно было спокойно сериализовывать настроенный экземпляр класса).
Честно говоря, я не вижу, что там сериализовать. Состояния у него, как такового, нету. А если логирование вводить, то тогда логи будут смешиваться. Честно говоря, я не вижу юзкейса. У тебя есть на примете какой-нибудь наглядный?
2. Коннектиться к БД не сразу, а в момент первого запроса.
Хорошая фича, но не первостепенная. Особенно для ЦА.
3. Прикрутить логирование выполняемых запросов, ну или подумать как его будешь делать. Желательно иметь возможность программно получать список запросов, которые выполнялись, возможно с некоторыми характеристиками типа "время", "кол-во возвращённых строк".
А вот логирование с профайлингом - как раз в ближайших планах, да. Я его выпилил из родительского класса, чтобы пока не мешало - оно там завязано на внутренние механизьмы.
Сможешь занять себе ещё на недельку вместо работы :D
Ну, впереди - пьяная неделя в январе. Хотя написание документации - куда более трудоемкий процесс :)
 

damner2

Новичок
Честно говоря, я не вижу, что там сериализовать. Состояния у него, как такового, нету. А если логирование вводить, то тогда логи будут смешиваться. Честно говоря, я не вижу юзкейса. У тебя есть на примете какой-нибудь наглядный?
У меня, например, кешируется экзмпляр класса настроенного фреймворка на продакшене. Ну шоб быстрее было, шоб каждый раз не настраивать - выигрыш в основном получается за счёт отсутствия подгрузки конфигурационных данных. Экземпляр класса для работы с БД может быть свойством, которое должно будет сериализоваться. При сериализации нужно предусмотреть чтоб в строку не попадал ресурс коннекта к БД.
 

fixxxer

К.О.
Партнер клуба
если честно, то я нихрена не понял.
Буду пробовать ещё
1) плейсхолдер ?q с подстановкой as-is
2) метод rawQuery, который - а не query() - теперь дергается во всех get() - и он не выполняет парсинг а просто выполняет данный ему запрос - иначе парсинг вызывается 2 раза всегда для get*-функций (хз как оно вообще у тебя работало ;) видимо ? вообще в данных не встречался)
 

Фанат

oncle terrible
Команда форума
1) плейсхолдер ?q с подстановкой as-is
2) метод rawQuery, который - а не query() - теперь дергается во всех get() - и он не выполняет парсинг а просто выполняет данный ему запрос - иначе парсинг вызывается 2 раза всегда для get*-функций
Так не будет работа половина функционала - query сама по себе парсит будьздоров - в этом вся фишка.
(хз как оно вообще у тебя работало ;) видимо ? вообще в данных не встречался)
Не-не-не, Девид Блейн!
в обычных условиях двойного парсинга не происходит. parse() никогда не вызывается для выполнения запроса
Смотри, у нас, грубо говоря, три функции
1. prepareQuery() - она парсит.
2. parse() - вызывает prepareQuery() и вызывает текст. Вызывается только для части запроса.
3. query() - вызывает prepareQuery() и исполняет запрос.

Если мы дёргаем query(), она дергает prepareQuery(), получает запрос и исполняет. Никакого двойного парсинга нет.
поэтому-то я и напихал в фак кучу примеров с кодом - том простой запрос с плейсхолдером ?u

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

Но смотри, что предложел C - это ж гениально! parse() искейпит знак вопроса - и всё! Так мы разом перебили всех зайцев.
единственный минус - портится запрос (с вопросами), который вывели через $parse для дебага. Ну так это не жалко.
 

fixxxer

К.О.
Партнер клуба
Не, это то я все понял.
Дело в том, что если вызывать не query(), а скажем getAll() то prepareQuery() вызовется 2 раза - в самой getAll() и потом в query().
Вот этот двойной вызов я и убрал. И все работает, и не надо никаких эскейпов.
Вообще конечно надо бы юнит тестами покрыть, но тогда бы еще коннект вынести в отдельную функцию, а то мокать сложно.
 

Фанат

oncle terrible
Команда форума
Не, это то я все понял.
Дело в том, что если вызывать не query(), а скажем getAll() то prepareQuery() вызовется 2 раза - в самой getAll() и потом в query().
Вот этот двойной вызов я и убрал. И все работает, и не надо никаких эскейпов.
Вообще конечно надо бы юнит тестами покрыть, но тогда бы еще коннект вынести в отдельную функцию, а то мокать сложно.
ЁЖ ТЫ Ж МОЁ. и правда.
тогда я тоже не понимаю, как оно раньше работало.
 

Фанат

oncle terrible
Команда форума
вот черт.
в оригинальном классе всё нормально
а вынося в отдельный, я решил оптимизировать, дебил, заменив start() и getRes() одной - query().
 

fixxxer

К.О.
Партнер клуба
К вопросу о рефакторинге без тестов =)

А raw-плейсхолдер нужен, иначе невозможно отличить, где мы подставляем уже распарсенный результат, а где собираем сам шаблон из кусочков (это ведь тоже может понадобиться). Эскейпить все плейсхолдеры - это извращение, мне кажется: типизированные плейсхолдеры для того и нужны, чтобы четко сказать, чего мы хотим, а не надеяться на какую-то магию внутри.
 

Фанат

oncle terrible
Команда форума
а где собираем сам шаблон из кусочков
Если собираем шаблон - то и не парсим - не?
или я не понял задачу. Приведи пример?

Эскейпить все плейсхолдеры
Мы искейпим не плейсхолдеры.
Мы тупо искейпим результат парсинга, в котором может встретиться знак вопроса

PHP:
$one = 'some text with ?s placeholders';
$where = $db->parse("one = ?s",$one); // one = 'some text with ??s placeholders'
$sql  = $db->getRow("SELECT * FROM table WHERE $where");
Вуаля - ноль ошибок!

или я опять тебя не понял?
 

fixxxer

К.О.
Партнер клуба
edited: туплю, вроде все нормально получается

да, можно так )
 

Crys

Двинутый новичок
Чота я не понял.. Это создается DbSimple с расширенными плейсхолдерами (?d, ?s etc..)? Я для "безопасности" лет семь назад дальше пошел - выносил шаблоны запросов вообще за пределы кода, где они вызываются. То есть запросы у меня были типа $db->fetchAll('path.to.file.with.any.queries.my_query',$a,$b,$c);

Имхо, провальная затея.

Хомо-саппиенс-неразумный даже услышав совет использовать эту библиотеку, будет в случае проблем гуглить и юзать найденные запросы с mysql_query

А разумному будет реально все-равно, составлять по документации запросы в конструкторе в рамках фреймворка, эскейпить переменные при вставке в mysql_query или юзать любую нормальную библиотеку

Или я что-то недопонял?
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Crys разумному тимлиду будет реально НЕ все-равно во что ткнуть носом свою стаю обезьян
мне нравятся типизированные плейсхолдеры
 

~WR~

Новичок
Вуаля - ноль ошибок!
Нет, тогда в обычном запросе знаки вопроса удвоятся.

PHP:
$one = 'some text with ?s placeholders';
$sql  = $db->getRow("SELECT * FROM table WHERE one = ?s", $one); //SELECT * FROM table WHERE one =  'some text with ??s placeholders'
Можно разбить на две функции: одна для сборки части запроса, а другая - для сборки запроса целиком. Но тогда нужно будет каждый раз думать, какую из них вызывать. Особенно интересно станет, когда понадобится собрать большой и сложный запрос, а не тривиальный select. Ведь один кусочек может входить в другой кусочек.
 

Фанат

oncle terrible
Команда форума
Нет, тогда в обычном запросе знаки вопроса удвоятся.
Это-то нам и надо!
У нас вопрос изначально должен быть удвоен - parseQuery оставляет от удвоенного один.
Так что кругом профит!

В то, что сам знак вопроса нужно экранировать, меня ткнул носом Вурдалак ещё в прошлом топике, полтора года назад.
То есть, правила, на самом деле, ТРЕБУЮТ, чтобы в шаблоне запросе все вопросительные знаки были удвоены.
поскольку parse() добавляет ШАБЛОНУ - то она и должна удваивать. А она этого не делала.
Так что теперь всё сошлось, все кусочки пазла
 

~WR~

Новичок
Пример:
PHP:
$one = 'some text with ?s placeholders';

$where = $db->parse('one = ?s', $one);                           //some text with ??s placeholders
$sub_query = $db->parse('SELECT * FROM table WHERE ' . $where);  //some text with ????s placeholders
$db->getRow("SELECT * FROM ($sub_query) a WHERE 1");             //some text with ??s placeholders
Т.е. с каждым последовательным вызовом parse() вопросики будут размножаться.
А их stip будет происходить только один раз.

Хорошо, если я ошибаюсь, и проблемы реально нет.
Commit it. Код лучше тысячи слов. :)
 

Фанат

oncle terrible
Команда форума
Можно разбить на две функции: одна для сборки части запроса, а другая - для сборки запроса целиком.
Ну, собственно, Фикс всё и сделал своим патчем, только дошло это до меня сильно не сразу. Но оно уже тогда работало :)
raw-плейсхолдер и является той самой "функцией для разборки частей запроса"!
В итоге код http://www.phpfaq.ru/examples#multiparse отрабатывает корректно!
 
Сверху