воскресенье, 30 июня 2019 г.

Как подключить трубку к базе Panasonic?

remont-fridge-tv.ru

Как подключить трубку к базе Panasonic?

Автор: Профикомп

Ответ мастера:
Телефонные устройства стандарта DECT, имеющие расширение GAP, сделаны таким образом, что любая база может быть подключена к любой трубке. Правда, существуют отличия в разъемах у трубок разных производителей. То, как сопрягается трубка с базой, зависит от того, какова структура ее меню.
Если DECT-аппаратом не поддерживается стандарт GAP, к нему может быть подключена только одна трубка, причем лишь от аппарата такой же модели. Для начала заряженная трубка должна быть установлена на базу. Потом нужно выключить и снова включить блок питания. Далее необходимо подождать, пока трубка не подаст сигнал, и значок антенны на экране не закончит мигать. После этого трубка сопряжена с базой, а предыдущая уже не сопряжена.
Если аппаратом поддерживается стандарт GAP, то одна база может сопрягаться с несколькими трубками, и этот процесс будет осуществляться через меню. Если трубка не может быть совмещена с базой по разъему, то того чтобы зарядить ее, стоит использовать отдельную зарядную базу, которая не содержит радиопередающих узлов.
Для аппаратов Panasonic, чтобы выполнить сопряжение, нужно проделать следующее. В меню находим пункт «Регистрация трубки». Выбираем его. На базе находим специальную кнопку небольшого размера, нажать на которую нельзя пальцем. Если такая кнопка отсутствует, то нужно воспользоваться кнопкой "intercom". Если и ее нет, то используем кнопку поиска базы. Нажимаем на нее и удерживаем на протяжении нескольких секунд.
Если произошло срабатывание функции поиска трубки, то значит, что мы нажимали кнопку недостаточно. Нажимаем на нее снова дважды. При этом первый раз недолго, а второй – на протяжении достаточно продолжительного времени.
Экран трубки покажет форму ввода кода регистрации. Введем его. Если ранее этот код в памяти базы не изменялся, то его значение равно 0000. Если этот код не подходим, осмотрим базу со всех сторон. Может быть, код регистрации есть прямо на ней. Если код код найти все-таки не удалось, нужно обратиться в любую официальную мастерскую производителя. Необходимо обязательно предоставить туда базу, для того чтобы специалисты могли убедиться в том, что она действительно принадлежит вам.
Если все же оказалось, что у кода значение 0000, то следует изменить его на новый, после чего записать на дне базы. Ни в коем случае не нужно показывать его соседям.

четверг, 27 июня 2019 г.

Как пронумеровать страницы в Либре Офис

http://lumpics.ru/how-to-number-the-pages-in-libre-office/

Как пронумеровать страницы в Либре Офис



Либре Офис является прекрасной альтернативой знаменитому и раскрученному Microsoft Office Word. Пользователям нравится функционал LibreOffice и особенно то, что эта программа бесплатная. К тому же, здесь есть подавляющее большинство функций, присутствующих в продукте от мирового IT гиганта, в том числе и нумерация страниц.

Вариантов нумерации страниц в LibreOffice есть сразу несколько. Так номер страницы можно вставить в верхний или нижний колонтитул или же просто как часть текста. Рассмотрим каждый вариант более подробно.
Скачать последнюю версию Либре Офис

Вставляем номер страницы

Итак, чтобы просто вставить номер страницы как часть текста, а не в колонтитул, нужно сделать следующее:
  1. В выпадающем списке выбрать «Номер страницы».
  2. В панели задач сверху выбрать пункт «Вставка».
  3. Найти пункт под названием «Поле», навести на него.
После этого номер страницы будет вставлен в текстовый документ.
Недостатком этого способа является то, что на следующей странице уже не будет отображаться номер страницы. Поэтому лучше использовать второй способ.
Что касается вставки номера страницы в верхний или нижний колонтитул, то здесь все происходит так:
  1. Сначала нужно выбрать пункт меню «Вставка».
  2. Затем следует перейти к пункту «Колонтитулы», выбрать, нужен ли нам верхний или нижний.
  3. После этого останется просто навести на нужный колонтитул и нажать на надпись «Базовый».
  4. Теперь, когда колонтитул стал активным (курсор находится на нем), следует сделать то же самое, что описано выше, то есть зайти в меню «Вставка», затем выбрать «Поле» и «Номер страницы».
После этого на каждой новой странице в нижнем или верхнем колонтитуле будет отображаться ее номер.
Иногда требуется сделать нумерацию страниц в Либре Офис не для всех листов или же начать нумерацию заново. В LibreOffice можно сделать и это.

Редактирование нумерации

Для того чтобы убрать нумерацию на определенных страницах, к ним нужно применить стиль «Первая страница». Этот стиль и отличается тем, что он не дает возможности страницам быть пронумерованными, даже если в них активен колонтитул и поле «Номер страницы». Чтобы поменять стиль, нужно выполнить следующие простые шаги:
  1. Открыть на верхней панели пункт «Формат» и выбрать «Титульная страница».
  2. В открывшемся окне возле надписи «Страница» нужно указать, для каких страниц будет применен стиль «Первая страница» и нажать кнопку «ОК».
  3. Чтобы указать, что не будет пронумерована эта и следующая за ней страница, необходимо возле надписи «Количество страниц» написать цифру 2. Если этот стиль нужно применить к трем страницам, указываем «3» и так далее.
К сожалению, здесь нет возможности сразу через запятую указать, какие страницы не следует нумеровать. Поэтому, если речь идет о страницах, которые не следуют друг за другом, нужно будет заходить в это меню несколько раз.
Чтобы пронумеровать страницы в LibreOffice заново, нужно сделать следующее:
  1. Поставить курсор на той странице, с которой нумерация должна начаться заново.
  2. Зайти в верхнем меню в пункт «Вставка».
  3. Нажать на «Разрыв».
  4. В открывшемся окне поставить галочку напротив пункта «Изменить номер страницы».
  5. Нажать кнопку «ОК».
По необходимости здесь можно выбрать и не номер 1, а какой угодно.
Для сравнения: Как пронумеровать страницы в Microsoft Word
Итак, мы разобрали процесс добавления нумерации в документ LibreOffice. Как видим, все выполняется очень просто, и разобраться с этим сможет даже начинающий пользователь. Хотя на этом процессе можно видеть разницу между Microsoft Word и LibreOffice. Процесс нумерации страниц в программе от Microsoft намного более функционален, есть великое множество дополнительных функций и особенностей, благодаря которым документ можно сделать по-настоящему особенным. В LibreOffice все намного скромнее.

Настраиваем драйвер принтера Xerox WC 5955



Добрый день!
Часто у на предприятии возникают проблемы с правильной печатью на принтере Xerox WC 5955. Мы рассмотрим, как правильно настроить драйвер принтера. Для начала нужно понять, что у драйвера могут быть две настройки. Первая «глобальная» она работает для всех приложений. Вторая «локальная» работает в приложении с которым мы работаем. «Локальная» имеет более высокий приоритет, но тем не менее по умолчанию в приложении работает «глобальная» настройка.
Для начала рассмотрим, как поменять «глобальные» настройки:
1.                  Сначала запустим Панель управления. Для этого в строке поиска стартового меню наберем «Панель управления»





2.                  Если в панели управления не выставлен вид в виде Категории – выставим так:



3.                  Выберите пункт «Просмотр устройств и принтеров».





 
4.  В контекстном меню нужного принтера выберите пункт «Свойства принтера» (не путайте с пунктом «Свойства»).


5.                  В новом окне выберите пункт «Настройка»



6.                  Следующее окно будет меняться от принтера к принтеру, но принцип остается неизменным.
Нужно найти настройку лотка.







Еще одной распространённой проблемой драйвера является настройка двухсторонней печати.
7.                  Для ее решения после пункта 4 перейдем далее

Теперь можем поговорить об установке правильной ориентации документа. Вообще принтер как правило сам определяет этот параметр, но иногда его нужно устанавливать самостоятельно.
8.                  Чтобы установить ориентацию документа следуйте приведенным шагам:





 
Вообще, то что я объясняю работа системного администратора, но думаю эта статья, тем не менее, будет кому-то еще полезна. Спасибо за внимание!

PHP: SQL-инъекции - Manual

php.net

PHP: SQL-инъекции - Manual


SQL-инъекции

Многие веб-разработчики даже не догадываются, что SQL-запросы могут быть подделаны, и считают, что SQL-запросы всегда достоверны. На самом деле поддельные запросы могут обойти ограничения доступа, стандартную проверку авторизации, а некоторые виды запросов могут дать возможность выполнять команды операционной системы.
Прямое внедрение вредоносных инструкций в SQL-запросы - это методика, в которой взломщик создает или изменяет текущие SQL-запросы для отображения скрытых данных, их изменения или даже выполнения опасных команд операционной системы на сервере базы данных. Атака выполняется на базе приложения, строящего SQL-запросы из пользовательского ввода и статических параметров. Следующие примеры, к сожалению, построены на реальных фактах.
Благодаря отсутствию проверки пользовательского ввода и соединению с базой данных под учетной записью суперпользователя (или любого другого пользователя, наделенного соответствующими привилегиями), взломщик может создать еще одного пользователя БД с правами суперпользователя.
Пример #1 Постраничный вывод результата... и создание суперпользователя в PostgreSQL
<?php
$offset 
= $argv[0]; // внимание, нет проверки вводимых данных!$query  = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";$result = pg_query($conn, $query);?>
Обычно пользователи кликают по ссылкам 'вперед' и 'назад', вследствие чего значение переменной $offset заносится в
URL. Скрипт ожидает, что $offset - десятичное число. Однако, взломщик может попытаться взломать систему, присоединив к
URL дополнительную строку, обработанную функцией urlencode():
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--
Если это произойдет, скрипт предоставит взломщику доступ к базе с правами суперпользователя. Заметим, что значение 0; использовано для того, чтобы задать правильное смещение для первого запроса и корректно его завершить.
Замечание:
Часто используемой техникой для игнорирования SQL-парсером оставшейся части запроса является использование --, означающей комментарий.
Еще один вероятный способ получить пароли учетных записей в БД - атака страниц, предоставляющих поиск по базе. Взломщику нужно лишь проверить, используется ли в запросе передаваемая на сервер и необрабатываемая надлежащим образом переменная. Это может быть один из устанавливаемых на предыдущей странице фильтров, таких как WHERE, ORDER BY, LIMIT и OFFSET, используемых при построении запросов SELECT. В случае, если используемая вами база данных поддерживает конструкцию UNION, взломщик может присоединить к оригинальному запросу еще один дополнительный, для извлечения пользовательских паролей. Настоятельно рекомендуем использовать только зашифрованные пароли.
Пример #2 Листинг статей... и некоторых паролей (для любой базы данных)
<?php
$query  
= "SELECT id, name, inserted, size FROM products
           WHERE size = '$size'";$result = odbc_exec($conn, $query);?>
Статическая часть запроса может комбинироваться с другим SELECT-запросом, который выведет все пароли:
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
Если этот запрос (использующий ' и --) присоединить к значению одной из переменных, используемых для формирования $query, то запрос заметно преобразится.
Команды UPDATE также могут использоваться для атаки. Опять же, есть угроза разделения инструкции на несколько частей и присоединения дополнительного запроса. Также взломщик может видоизменить выражение SET. В этом случае потенциальному взломщику необходимо обладать некоторой дополнительной информацией о структуре базы данных для успешного манипулирования запросами. Эту информацию можно получить, проанализировав используемые в форме имена переменных, либо просто перебирая все наиболее распространенные варианты названия соответствующих полей (а их не так уж и много).
Пример #3 От восстановления пароля... до получения дополнительных привилегий (для любой базы данных)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";?>
Но злоумышленник может ввести значение ' or uid like'%admin%' для переменной $uid для изменения пароля администратора или просто присвоить переменной $pwd значение hehehe', trusted=100, admin='yes для получения дополнительных привилегий. При выполнении запросы переплетаются:
<?php// $uid: ' or uid like '%admin%$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";// $pwd: hehehe', trusted=100, admin='yes$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;";?>
Пугающий пример того, как на сервере баз данных могут выполняться команды операционной системы.
Пример #4 Выполнение команд операционной системы на сервере (для базы MSSQL)
<?php
$query  
= "SELECT * FROM products WHERE id LIKE '%$prod%'";$result = mssql_query($query);?>
Если взломщик введет значение a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- для переменной $prod, тогда запрос $query будет выглядеть так:
<?php
$query  
= "SELECT * FROM products
           WHERE id LIKE '%a%'
           exec master..xp_cmdshell 'net user test testpass /ADD' --%'";$result = mssql_query($query);?>
MSSQL сервер выполняет SQL-команды в пакетном режиме, в том числе и операции по заведению локальных учетных записей базы данных. В случае, если приложение работает с привилегиями администратора sa и сервис MSSQL запущен с необходимыми привилегиями, то выполнив приведенные выше действия, взломщик получит аккаунт для доступа к серверу.
Замечание:
Некоторые приведенные в этой главе примеры касаются конкретной базы данных. Это не означает, что аналогичные атаки на другие программные продукты невозможны. Работоспособность вашей базы данных может быть нарушена каким-либо другим способом.
Авторство изображения принадлежит » xkcd

Способы защиты

Хотя по-прежнему очевидно, что взломщик должен обладать по крайней мере некоторыми знаниями о структуре базы данных чтобы провести успешную атаку, получить эту информацию зачастую очень просто. Например, если база данных является частью open-source или другого публично доступного программного пакета с инсталляцией по умолчанию, эта информация является полностью открытой и доступной. Эти данные также могут быть получены из закрытого проекта, даже если он закодирован, усложнен, или скомпилирован, и даже из вашего личного кода через отображение сообщений об ошибках. К другим методам относится использование распространенных (легко угадываемых) названий таблиц и столбцов. Например, форма логина, которая использует таблицу 'users' c названиями столбцов 'id', 'username' и 'password'.
Большинство успешных атак основывается на коде, написанном без учета соответствующих требований безопасности. Не доверяйте никаким вводимым данным, особенно если они поступают со стороны клиента, даже если это списки в форме, скрытые поля или куки. Первый приведенный пример показывают, как подобные запросы могут привести к катастрофе.
  • Никогда не соединяйтесь с базой данных, используя учетную запись владельца базы данных или суперпользователя. Всегда старайтесь использовать специально созданных пользователей с максимально ограниченными правами.
  • используйте подготовленные выражения с привязанными переменными. Эта возможность предоставляется расширениями PDO, MySQLi и другими библиотеками.
  • Всегда проверяйте введенные данные на соответствие ожидаемому типу. В PHP есть множество функций для проверки данных: начиная от простейших функций для работы с переменными и функций определения типа символов (таких как is_numeric() и ctype_digit() соответственно) и заканчивая Perl-совместимыми регулярными выражениями.
  • В случае, если приложение ожидает цифровой ввод, примените функцию ctype_digit() для проверки введенных данных, или принудительно укажите их тип при помощи settype(), или просто используйте числовое представление при помощи функции sprintf().
    Пример #5 Более безопасная реализация постраничной навигации
    <?php
    settype
    ($offset, 'integer');$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";// обратите внимание на формат %d, использование %s было бы бессмысленно$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
                     $offset);?>
  • Если на уровне базы данных не поддерживаются привязанные переменные, то всегда экранируйте любые нечисловые данные, используемый в запросах к БД при помощи специальных экранирующих функций, специфичных для используемой вами базы данных (например, mysql_real_escape_string(), sqlite_escape_string() и т.д.). Общие функции такие как addslashes() полезны только в определенных случаях (например MySQL в однобайтной кодировке с отключенным NO_BACKSLASH_ESCAPES), поэтому лучше избегать их использование.
  • Ни в коем случае не выводите никакой информации о БД, особенно о ее структуре. Также ознакомьтесь с соответствующими разделами документации: "Сообщения об ошибках" и "Функции обработки и логирования ошибок".
  • Вы можете использовать хранимые процедуры и заранее определенные курсоры для абстрагированной работы с данными, не предоставляя пользователям прямого доступа к данным и представлениям, но это решение имеет свои особенности.
Помимо всего вышесказанного, вы можете логировать запросы в вашем скрипте либо на уровне базы данных, если она это поддерживает. Очевидно, что логирование не может предотвратить нанесение ущерба, но может помочь при трассировке взломанного приложения. Лог-файл полезен не сам по себе, а информацией, которая в нем содержится. Причем, в большинстве случаев полезно логировать все возможные детали.

Примеры использования класса safemysql.

phpfaq.ru

Примеры использования класса safemysql.


Не пропусти главное событие года - Конференцию веб-разработчиков DevConf!
Москва, 21-22 июня.
Простые запросы
Пример получения колонки и подстановки массива
Условная сборка WHERE
Примеры использования белых списков:
Случай сложной вставки.
Множественный insert
пример с ON DUPLICATE
Пример сложносочинённого запроса
Вставка в запрос оператора NULL
Множественный парсинг
Комментарии (42)
Простые запросы
$name = $db->getOne('SELECT name FROM table WHERE id = ?i',$_GET['id']);$data = $db->getInd('id','SELECT * FROM ?n WHERE id IN (?a)','table', array(1,2));$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i",$table,$mod,$limit);
Пример получения колонки и подстановки массива
$ids  = $db->getCol("SELECT id FROM tags WHERE tagname = ?s",$tag);$data = $db->getAll("SELECT * FROM table WHERE category IN (?a)",$ids);
Условная сборка WHERE
$w = array();$where = '';
if ($one) $w[] = $db->parse("one = ?s",$one);
if ($two) $w[] = $db->parse("two IN (?a)",$two);
if ($tre) $w[] = $db->parse("tre <= ?i",$tre);
if (count($w)) $where = "WHERE ".implode(' AND ',$w);$data = $db->getAll("SELECT * FROM table ?p LIMIT ?i,?i",$where, $start,$per_page);

Примеры использования белых списков:
$allowed = array('title','url','body','rating','term','type');$data    = $db->filterArray($_POST,$allowed);$sql     = "INSERT INTO ?n SET ?u";$db->query($sql,$table,$data);
Функция whiteList служит для проверки переданной строки по белому списку.
пример "мягкой" проверки:
$order = $db->whiteList($_GET['order'],array('name','price'),'name');$dir   = $db->whiteList($_GET['dir'],array('ASC','DESC'),'ASC');$sql   = "SELECT * FROM table ORDER BY $order $dir LIMIT ?i,?i"$data  = $db->getAll($sql, $start, $per_page);
здесь мы проверяем имя поля и направление сортировки. Если соответствие не нашлось, то автоматически подставляется дефолтное значение.
Но по-хорошему, если нам вместо нормального имени поля прислали неведомую ерунду - надо бы ответить адекватной ошибкой. В этом случае нужно просто не указывать дефолтное значение:
$order = $db->whiteList($_GET['order'],array('name','price'));
if ($order === FALSE) {
    throw http404; // или ваш собственный способ выдать 404 ошибку}

Случай сложной вставки.
Гибкость, о которой говорилось выше, заключается в том, что мы не ограничены синтаксисом специализированной функции, например, insert. К примеру, чтобы выполнить запрос INSERT IGNORE, мы просто пишем
$sql = "INSERT IGNORE INTO ?n SET ?u";$db->query($sql,$table,$data);
а в случае с функцией нам пришлось бы дописывать к ней новые параметры, нагромождая новый, неочевидный синтаксис.
Или такой пример: основной массив данных лежит в $data, но при этом два поля заполняются вручную,
поскольку в них используются функции mysql.
$data = array('field'=>$value,'field2'=>$value);$sql  = "INSERT INTO table SET ts=unix_timestamp(), ip=inet_aton(?s),?u";$db->query($sql, $ip, $data);
обычной функцией insert() мы бы не смогли здесь воспользоваться
Множественный insert
$ins = array();
foreach ($data as $row) {
    $ins[] = $db->parse("(NULL,?s,?s, NOW())",$row['name'],$row['lastname']);
}$instr = implode(",",$ins);$db->query("INSERT INTO table VALUES ?p",$instr);

пример с ON DUPLICATE
$data = array('offers_in' => $in, 'offers_out' => $out);$sql  = "INSERT INTO stats SET pid=?i,dt=CURDATE(),?u ON DUPLICATE KEY UPDATE ?u";$db->query($sql,$pid,$data,$data);
Пример сложносочинённого запроса
Имея массив
$count = array(1 => 10, 2 =>13, 5 => 123, 17 => 43)
получить запрос
UPDATE `rules`SET `used_at` = NOW(), `times_used` = `times_used` + CASE `id`WHEN 1 THEN 10
WHEN 2 THEN 13
WHEN 5 THEN 123
WHEN 17 THEN 43
END
WHERE id IN(1, 2, 5, 17)

С использованием функции parse() получается довольно компактный код
$when = '';
foreach($count as $rule_id => $rule_count) {
  $when .= $db->parse("WHEN ?i THEN ?i ",$rule_id, $rule_count);
}$sql = "UPDATE rules SET used=NOW(),times=times + CASE id ?p END WHERE id IN(?a)");$db->query($sql,$when,array_keys($count));

Вставка в запрос оператора NULL
Я долго размышлял над этим вопросом, и сначала решил не добавлять NULL автоматом.
Но потом на гитхабе получил issue от человека, который передавал AJAX-ом JSON, в котором была куча NULL-в, и которому было бы дико удобно сразу пихать раскодированный json в запрос, одной строчкой:
$db->query("INSERT INTO t SET ?u", json_decode($input));
Но нуллы ему все портили и он уговорил меня переделать.
А потом я уже узнал, что PDO и Mysqli тоже отправляют NULL в базу, если ты делаешь prepared statement и передаешь в него переменную, которая содержит NULL. Так что это самое правильное поведение.
В итоге, сейчас такой код
echo $db->parse("INSERT INTO t (col) VALUES (?s)", NULL);
выведет
INSERT INTO t (col) VALUES (NULL)
- что, оказывается, невероятно удобно!
Множественный парсинг
$text   = "text with ?s placeholder. let's go";$where  = $db->parse("name = ?s",$text);$subsel = $db->parse("SELECT id FROM test WHERE ?p",$where);$data   = $db->getAll("SELECT * FROM test WHERE id IN(?p) OR id>?i",$subsel,1);

среда, 26 июня 2019 г.

PHP класс для удобной и безопасной работы с MySQL

habr.com

PHP класс для удобной и безопасной работы с MySQL


После написания статьи про защиту от инъекций я взялся за написание класса, реализующего изложенные в ней идеи.
А точнее, поскольку ключевой функционал уже использовался в рамках рабочего фремворка, я занялся выделением его в самостоятельный класс. Пользуясь случаем, хочу поблагодарить участников PHPClub-а за помощь в исправлении нескольких критических ошибок и полезные замечания. Ниже я постараюсь описать основные особенности, но сначала небольшой
дисклеймер
Есть несколько способов работы с SQL — можно использовать квери-билдер, можно ORM, можно работать с чистым SQL. Я избрал последний вариант, потому что мне он ближе. Я совсем не считаю первые два плохими. Просто лично мне всегда было тесно в их рамках. Но я ни в коем случае не утверждаю, что мой вариант лучше. Это просто ещё один вариант. Который можно использовать, в том числе, и при написании ORM-а. В любом случае, я считаю, что наличие безопасного способа работать с чистым SQL не может принести какой-либо вред. Но при этом, возможно, поможет последним оставшимся приверженцам использования mysql_* в коде приложения, отказаться, наконец, от этой порочной практики.
В двух словах, класс строится вокруг набора функций-хелперов, позволяющих выполнять большинство операций с БД в одну строку, обеспечивая при этом (в отличие от стандартных API) полную защиту от SQL инъекций, реализованную с помощью расширенного набора плейсхолдеров, защищающих любые типы данных, которые могут попадать запрос.
В основу класса положены три базовых принципа:
  1. 100% защита от SQL инъекций
  2. При этом защита очень удобная в применении, делающая код короче, а не длиннее
  3. Универсальность, портабельность и простота освоения
Остановлюсь чуть подробнее на каждом из пунктов.

Безопасность

обеспечивается теми самыми двумя правилами, которые я сформулировал в статье:
  1. Любые — без исключений! — динамические элементы попадают в запрос только через плейсхолдеры.
  2. Всё, что не получается подставить через плейсхолдеры — прогоняется сначала через белый список.
К сожалению, стандартные библиотеки не предоставляют полной защиты от инъекций, защищая с помощью prepared statements только строки и числа.
Поэтому, чтобы сделать защиту полной, пришлось отказаться от заведомо ограниченной концепции prepared statements в пользу более широкого понятия — плейсхолдеров. Причём плейсхолдеров типизованных (эта вещь всем нам известна по семейству функций printf(): %d — плейсхолдер, который подсказывает парсеру, как обрабатывать подставляемое значение, в данном случае — как целое число). Нововведение оказалось настолько удачным, что разом решило множество проблем и значительно упростило код. Подробнее о типизованных плейсхолдерах я напишу ниже.
Поддержка же фильтрации по белым спискам обеспечивается двумя функциями, несколько притянутыми за уши, но, тем не менее, необходимыми.

Удобство и краткость кода приложения

Здесь мне также здорово помогли типизованные плейсхолдеры, которые позволили сделать вызовы функций однострочными, передавая сразу и запрос, и данные для него. Плюс набор хелперов, напоминающих таковые в PEAR::DB — функций, сразу возвращающих результат нужного типа. Все хелперы организованы по одной и той же схеме: в функцию передаётся один обязательный параметр — запрос с плейсхолдерами, и сколько угодно опциональных параметров, количество и порядок которых должны совпадать с количеством и порядком расположения плейсхолдеров в запросе. У функций семейства Ind используется ещё один обязательный параметр — имя поля, по которому осуществляется индексация возвращаемого массива.
Исходя из своего опыта, я пришёл к следующему набору возвращаемых значений (и, как следствие — хелперов):
  • query() — возвращает mysqli resource. Может использоваться традиционно, с fetch() и т.д.
  • getOne() — возвращает скаляр, первый элемент первой строки результата
  • getRow() — одномерный массив, первую строку результата
  • getCol() — одномерный массив скаляров — колонку таблицы
  • getAll() — двумерный массив, индексированный числами по порядку
  • getInd() — двумерный массив, индексированный значениями поля, указанного первым параметром
  • getIndCol() — массив скаляров, индексированный полем из первого параметра. Незаменимо для составления словарей вида key => value
В итоге большинство обращений к БД сводится одно-двух строчным конструкциям (вместо 5-10 при традиционном подходе):
$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i",$table,$mod,$limit);
В этом коде есть только необходимые и значащие элементы, но нет ничего лишнего и повторяющегося. Все потроха аккуратно упрятаны внутрь класса: хелпер getAll() позволяет получить сразу нужный результат без написания циклов в коде приложения, а типизованные плейсхолдеры позволяют безопасно добавлять в запрос динамические элементы любых типов без прописывания привязок (bind_param) вручную. Extra DRY код! В случаях использования плейсхолдеров ?a и ?u разница в количестве кода становится ещё больше:
$data = $db->getAll("SELECT * FROM table WHERE category IN (?a)",$ids);

Универсальность и простота освоения

стоят на трех китах:
  1. Очень маленький API — пол-дюжины плейсхолдеров и столько же хелперов.
  2. Мы работаем со старым добрым SQL, который не надо заново учить.
  3. На первый взгляд незаметная, но невероятно полезная функция parse(), которая изначально предназначалась только для отладки, но в итоге выросла до ключевого элемента при составлении сложных запросов.
В итоге все сложносочинённые запросы собираются по-старинке — например в цикле — но при этом при соблюдении всех правил безопасности!
Приведу небольшой пример (примеры посложнее можно найти в документации по ссылке внизу статьи):
Довольно частый случай, когда нам надо добавить в запрос условие при наличии переменной
$sqlpart = '';
if (!empty($var)) {
    $sqlpart = $db->parse(" AND field = ?s", $var);
}
$data = $db->getAll("SELECT * FROM table WHERE a=?i ?p", $id, $sqlpart);
Здесь важно отметить несколько моментов.
Во-первых поскольку мы не связаны родным API, никто не запрещает нам пропарсить не весь запрос целиком, а только его часть. Это оказывается супер-удобно для запросов, собирающихся в соответствии какой-либо логикой: мы парсим только часть запроса, а затем она подставляется в основной запрос через специальный «холостой» плейсхолдер, чтобы избежать повторного парсинга (и соблюсти правило «любые элементы подставляются только через плейсхолдер»).
Но, к сожалению, это является слабым местом всего класса. В отличие от всех остальных плейсхолдеров (которые, даже будучи использованы неверно, никогда не приведут к инъекции) некорректное использование плейсхолдера ?p может к ней привести.
Однако защита от дурака сильно усложнила бы класс, но при этом все равно никак не защитила бы от тупой вставки переменной в строку запроса. Поэтому я решил оставить как есть. Но если вы знаете способ, как без слишком большого оверинжиниринга решить эту проблему — я был бы благодарен за идеи.
Тем не менее, в итоге мы получили мощный и лёгкий генератор запросов, который с лихвой оправдывает этот небольшой недостаток.
Мощный потому, что мы не ограничены синтаксисом квери-билдера, «SQL-ем, написанным на PHP» — мы пишем чистый SQL.
Лёгкий потому, что весь API составления запросов состоит из полудюжины плейсхолдеров и функции parse()
Вот мой любимый пример — вставка с использованием функций Mysql
$data = array('field'=>$value,'field2'=>$value);
$sql  = "INSERT INTO table SET ts=unix_timestamp(), ip=inet_aton(?s),?u";
$db->query($sql, $ip, $data);
С одной стороны, мы сохраняем синтаксис SQL, с другой — делаем его безопасным, а с третьей — капитально сокращаем количество кода.

Подробнее о типизованных плейсхолдерах

Сначала ответим на вопрос, почему плейсхолдеры вообще?
Это, в общем, уже общее место, но, тем не менее, повторюсь — любые динамические данные должны попадать в запрос только через плейсхолдеры по следующим причинам:
  • самое главное — безопасность. Добавив переменную черз плейсхолдер, мы можем быть уверены в том, что она будет корректно отформатирована.
  • локальность форматирования. Это не менее важный момент. Во-первых, данные форматируются непосредственно перед попаданием в запрос, и не затрагивают исходную переменную, которая потом может быть использована где-то ещё. Во-вторых, данные форматируются ровно там, где нужно, а не до начала работы скрипта, как при magic quotes, и не в десяти возможных местах кода несколькими разработчиками, каждый из которых может понадеяться на другого.
Развивая эту концепцию далее, мы приходим к мысли, что пейсхолдеры обязательно должны быть типизованными. Но почему?
Тут я бы хотел ненадолго остановиться, и проследить историю развития программистской мысли в области защиты от инъекций.
Сначала был хаос — вообще никакой защиты, пихаем всё как есть.
Дальше стало не сильно лучше, с парадигмой «искейпим всё, что пришло в скрипт от пользователя» и кульминацией в виде magic quotes.
Дальше лучшие умы пришли к тому, что правильно говорить не об экранировании, а о форматировании. Поскольку форматирование не всегда сводится к одному искейпингу. Так в PDO появился метод quote(), который делал законченное форматирование строки — не только экранировал в ней спецсимволы, но и заключал её в кавычки, не надеясь на программиста. В итоге, даже если программист использовал эту функцию не на месте (например, для числа), то инъекция всё равно не проходила (а в случае с голым экранированием через mysql_real_escape_string она легко проходит, если мы поместили в запрос число, не заключая его в кавычки). Будучи же использованной для форматирования идентификатора, эта функция приводила к ошибке на этапе разработки, что подсказывало автору кода о том, что он немножечко неправ.
К сожалению, на этом авторы PDO и остановились, поскольку в головах разработчиков до сих пор крепко сидит мысль о том, что форматировать в запросе надо только строки. Но на самом деле в запросе гораздо больше элементов самых различных типов. И для каждого нужен свой собственный тип форматирования! То есть, единственный метод quote() нас никак не устроит — нужно много разных quotes. Причём не в качестве исключения, «нате вам quoteName()», а как одна из главных концепций: каждому типу — свой формат. Ну а раз типов форматирования оказывается много — тип надо как-то указывать. И типизованный плейсхолдер для этого подходит лучше всего.
Кроме того, типизованный плейсхолдер — это ОЧЕНЬ удобно!
Во-первых, потому что становится ненужным специальный оператор для привязки значения к плейсхолдеру (но при этом сохраняется возможность указать тип передаваемого значения!)
Во-вторых, раз уж мы изобрели типизованный плейсхолдер — мы можем налепить этих плейсхолдеров огромное количество, для решения множества рутинных задач по составлению SQL запросов.
В первую очередь сделаем плейсхолдер для идентификаторов — нам его отчаянно не хватает в реальной, а не воображаемой авторами стандартных API, жизни. Как только девелопер сталкивается с необходимостью динамически добавить в запрос имя поля — каждый начинает извращаться по-своему, кто в лес, кто по дрова. Здесь же всё унифицировано с остальными элементами запроса, и добавление идентификатора становится не сложнее добавления строки. Но при этом идентификатор форматируется не как строка, а в соответствии со своими собственными правилами — заключается в обратные кавычки, а внутри эти кавычки экранируются удвоением.
Дальше — больше. Следующая головная боль любого разработчика, когда-либо пытавшегося использовать стандартные prepared statements в реальной жизни — оператор IN(). Вуаля, у нас есть плейсхолдер и для этой операции! Подстановка массива становится не сложнее любых других элементов, плюс она унифицирована с ними — никаких отдельных функций, меняется всего лишь буква в плейсхолдере.
Точно таким же образом делаем и плейсхолдер для SET. Не удержусь и продемонстрирую, насколько простым становится код для такого замороченного запроса, как INSERT… ON DUPLICATE:
$data = array('offers_in' => $in, 'offers_out' => $out);
$sql  = "INSERT INTO stats SET pid=?i,dt=CURDATE(),?u ON DUPLICATE KEY UPDATE ?u";
$db->query($sql,$pid,$data,$data); 
В данный момент классом поддерживается 6 типов плейсхолдеров
  • ?s («string») — строки (а также DATE, FLOAT и DECIMAL).
  • ?i («integer») — целые числа.
  • ?n («name») — имена полей и таблиц
  • ?p («parsed») — для вставки уже обработанных частей запроса
  • ?a («array») — набор значений для IN (строка вида 'a','b','c')
  • ?u («update») — набор значений для SET (строка вида `field`='value',`field`='value')
Что вполне достаточно для моих задач, но этот набор всегда можно расширить любыми другими плейсхолдерами, например, для дробных чисел. Делать отдельный плейсхолдер для NULL я не вижу смысла — его можно всегда вписать прямо в запрос.
Автоматическую трансляцию PHP-шного NULL в SQL-евский NULL я решил не делать. Возможно, это чуть усложнит код (в тех редких случаях, когда это нужно), но зато уменьшит его неоднозначность.
Кстати, как многие могли заметить, этот класс во многом напоминает библиотеку DbSimple Дмитрия Котерова. Но у меня есть принципиальные расхождения с некоторыми идеями, заложенными в неё.
Во-первых, я противник любой магии, когда одна и та же функция может возвращать различный результат в зависимости от типа переданных данных. Это, возможно, чуть упрощает написание, но при этом чудовищно затрудняет сопровождение и отладку кода. Поэтому в моем классе вся магия сведена к минимуму, а все операции и типы данных всегда прописываются явно.
Во-вторых, в DbSimple немного, на мой взгляд, переусложнённый синтаксис. С одной стороны, фигурные скобки — гениальная идея. С другой — а зачем, если в нашем распоряжении вся мощь PHP? Поэтому я решил пойти другим путём и весто «внутренней» — заведомо ограниченной — логики ввёл «внешнюю», ограниченную лишь синтаксисом РНР. Главное, чтобы любые динамические элементы попадали в запрос только через плейсхолдеры, а остальное зависит лишь от фантазии разработчика (и функции parse()).
Код класса доступен на Гитхабе, github.com/colshrapnel/safemysql/blob/master/safemysql.class.php
Cheat sheet с основными командами и примерами: phpfaq.ru/misc/safemysql_cheatsheet_ru.pdf
Хорошее представление о возможностях можно получить на странице примеров документации (к сожалению, ещё не законченной), phpfaq.ru/safemysql
Там же есть ответы на часто задаваемые вопросы, такие как «почему ты не используешь родные prepared statements?» и пр.
Тем не менее, буду рад ответить на любые вопросы в комментариях, а так же улучшить по вашим замечаниям как сам класс, так и эту статью.

Примеры использования класса