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

Вступление
Безопасность
Удобство
Простота и универсальность
Комментарии (252)

Переход с функций mysql_*
Пример CRUD-приложения
Примеры использования
Вопросы и ответы


Вступление
Данный класс создавался для реализации следующих основополагающих принципов:
  • безопасность составления запросов
  • удобство работы с БД
  • простота и универсальность

  • К сожалению, встроенные в PHP библиотеки обеспечивают недостаточную безопасность и не дают вообще никакого удобства, заставляя писать один и тот же код сотни раз.
    Различные самописные библиотеки не обеспечивают либо должной безопасности, либо удобства, либо являются монстрообразными компонентами толстых фреймворков, требующими многодневного изучения.

    Поэтому я и решил написать простой класс, обеспечивающий необходимую функциональность, который можно и нужно использовать для замены классических mysql_query/mysql_fetch_array.

    Код доступен на Гитхабе: http://github.com/colshrapnel/safemysql/blob/master/safemysql.class.php

    Главными отличиями от существующих библиотек являются
  • поддержка типизованных плейсхолдеров
  • и функция parse(), которая позволяет обработать плейсхолдеры не во всём запросе разом, а только его части

  • Эти два отличия обеспечивают гарантированную безопасность в сочетании с невиданной гибкостью.

    Основной принцип работы таков:
    Большая часть операций выполняется в одну строчку. В боле сложных случаях всё делается по-старинке - запрос собирается вручную, НО - с использованием гарантированно безопасных плейсхолодеров.

    Безопасность
    Безопасность достигается за счёт соблюдения двух основных правил:
  • любые динамические данные должны попадать в запрос только через плейсхолдеры
  • идентификаторы и ключевые слова должны выбираться из белого списка, прописанного в нашем коде

  • На данный момент библиотека поддерживает 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')

    Первый служит для добавления строк, а так же всех типов данных, приравненных к ним. Второй - целых чисел.
    Третий - идентификаторый - служит для динамической вставки в запрос имен полей и таблиц.
    Он требуется потому, что правила форматирования идентификаторов отличаются от правил форматирования строк. Ограничивается он не одинарной кавычкой, а обратным апострофом, а экранируется не слешем, а удвоением.
    Назначение четвёртого - подставлять в запрос уже обработанные части запроса.

    Последние два введены для удобства. Очень часто бывает нужно подставить массив в оператор IN(). Классическими средствами это сделать затруднительно, а с помощью плейсхолдера - не сложнее, чем строку.
    Наличие плейсхолдера ?u позволяет избавиться от выделенных методов insert() и update(), сделав запросы более гибкими.

    При необходимости набор плейсхолдеров может быть легко расширен.

    Единственный случай, не охватываемый плейсхолдерами - это SQL операторы, такие, как AND, DESC и любые другие.
    В случае, если они добавляются в запрос динамически, то их надо помещать в запрос только из белого списка, заранее прописанного в скрипте.

    Следует учесть, что идентификаторы тоже желательно добавлять из белого списка, во всяком случае, при поступлении данных от пользователя, поскольку ему может быть запрещён доступ к определенным полям таблицы.

    Для такой фильтрации служат методы filterArray() и whiteList()

  • filterArray() выбрасывает из переданного первым параметром массива все элементы, которых нет во втором массиве. Используется для фильтрации массивов, передаваемых для плейсхолдера ?u
  • whiteList() просто проверяет переданный параметр на наличие в списке разрешённых значений

  • подробнее их использование будет показано на примерах

    Удобство

    Удобство достигается за счёт следующих факторов:

  • плейсхолдеры делают ненужным ручное экранирование и приведение типов
  • плейсхолдеры берут на себя рутинные функции по формированию частей запросов, таких, как IN и SET
  • методы-хелперы, позволяющие сразу получить искомый результат из запроса, без повторения в 100500-й раз одного и того же кода
  • метод parse() позволяет гибко формировать запросы, добавляя отдельные элементы по условию


  • Хелпер - функция, берущая на себя всю рутинную работу по обработке запроса, и сразу возвращающая нужный результат. В сочетании с плейсхолдерами позволяет сокращать код в 3-5 раз!
    Пример: допустим, нам надо получить информацию о пользователе по имени, полученного из GET запроса.
    Классический код будет таким:
    $name = mysql_real_escape_string($_GET['name']);
    $result = mysql_query("SELECT * FROM users where name='$name'");
    $user = mysql_fetch_array($result);


    Как это делается с помощью функции-хелпера? В одну строчку!
    $user = $db->getRow("SELECT * FROM users where name=?s",$_GET['name']);

    причём чем сложнее наш запрос, то больше выгода.

    Полный список функций приведён ниже. В основном они различаются типом возвращаемого результата.

    - query($query,$param1,$param2, ...) - возвращает mysqli resource. аналог mysqli_query
    - getOne($query,$param1,$param2, ...) - возвращает скаляр, первый элемент первой строки результата
    - getRow($query,$param1,$param2, ...) - одномерный массив, первую строку результата
    - getCol($query,$param1,$param2, ...) - одномерный массив скаляров - колонку таблицы.
    - getAll($query,$param1,$param2, ...) - двумерный массив, индексированный числами по порядку
    - getInd($key,$query,$par1,$par2, ...) - двумерный массив, индексированный значениями поля, указанного первым параметром
    - getIndCol($key,$query,$par1,$par2, ...) - массив скаляров, индексированный полем из первого параметра. Незаменимо для составления словарей вида key => value

    Как видно, имена функций напоминают таковые из PEAR::DB, но не во всём совпадают с ними.
    В первую очередь, сокращено их количество, поскольку поддержка плейсхолдеров сделала многие искусственные функции (типа limitQuery() или INSERT) попросту ненужными, а синтаксис запросов - более естественным и в тоже время - гибким.

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

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