Эффективное использование PDO в MySQL и не только. Защита от SQL инъекций.

05.08.201893
Решение проблем PDO является наиболее удобным интерфейсом для работы с различными базами данных, заметно повышающий эффективность и безопасность взаимодействия. Класс позволяет работать с десятком существующих БД, используя их собственные драйвера, что обеспечивает высокую производительность, а метод «prepared statement» обеспечивает надежную защиту от SQL инъекций. Но есть и свои подводные камни...

Введение в PDO

PDO является расширением PHP, предоставляющим разработчикам простой и универсальный интерфейс для доступа к различным БД, а так же берет на себя задачу по обработке внешних данных, значительно упрощая данный процесс. Использование для разных БД их "родных" драйверов позволяет добиться высокой производительности. PDO поддерживает любую систему управления базами данных, для которой существует PDO драйвер, что позволяет легко переходить от одной СУБД к другой, а так же работать одновременно с несколькими БД. Для того, чтобы проверить, какие из драйверов есть на вашем сервере, можно воспользоваться следующей конструкцией:
print_r(PDO::getAvailableDrivers());

Подключение к MySQL c помощью PDO

Способы подключения к различным БД могут немного отличаться, но, в основном, синтаксис совпадает.
try{
          $DB = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
}catch(PDOException $e){
          $e->getMessage();
}
$DB = null;	# закрытие соединения;
PDO умеет выбрасывать исключения при ошибках, поэтому не забываем любые действия оборачивать в конструкцию try/catch. Всего существует 3 режима ошибок, которые включаются с помощью метода setAttribute(); и параметра PDO::ATTR_ERRMODE. Но, стоит помнить, что ошибка при попытке соединения будет всегда вызывать исключение.
PDO::ERRMODE_SILENT: Стандартный режим, включенный по умолчанию. Именно этот режим обычно используется для отлавливания ошибок в расширениях mysql и mysqli.
PDO::ERRMODE_WARNING: Вызывает стандартный Warning, позволяя скрипту продолжить выполнение. Крайне удобен при отладке.
PDO::ERRMODE_EXCEPTION: Наиболее предпочтительный тип контроля, который позволяет выбрасывать исключения и обрабатывать ошибки в скрытом режиме.
$DB->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Вставка и обновление данных. Методы exec и prepare при Insert/Update запросах

Самый простой вариант вставки новых данных может состоять из одного или двух шагов. Двухшаговый метод имеет большие преимущества, так как помогает в защите от SQL инъекций
$STH = $DB->exec("INSERT INTO table_name(name) VALUES('Майк')");	# небезопасный метод;
$STH = $DB->prepare("INSERT INTO table_name(name) VALUES('Майк')");          # метод с prepared statements;
$STH->execute();
Prepared statement — это заранее обработанный SQL-запрос, который может быть многократно выполнен путем отправки серверу различных наборов данных. Дополнительным преимуществом является невозможность провести SQL-инъекцию через данные, используемые в placeholders. Есть два варианта передачи placeholders # безымянные placeholders
$STH = $DB->prepare("INSERT INTO table_name(name, addr, city) values(?, ?, ?)"); # назначаем переменные каждому placeholder, с индексами от 1 до 3
$STH->bindParam(1, $name);
$STH->bindParam(2, $addr);
$STH->bindParam(3, $city);         # вставляем одну строку
$name = "Антон"; $addr = "д. 16 ул. Весеняя"; $city = "Москва";
$STH->execute();          # вставляем еще одну строку, уже с другими данными
$name = "Вован"; $addr = "д. 15 ул. Осеняя"; $city = "Амстердам";
$STH->execute(); 
Первым шагом мы составляем SQL запрос, после чего назначаем всем placeholders переменные. Далее назначаем переменным значения и выполняем запрос. Для последующих запросов достаточно изменить значения переменных и выполнить запрос еще раз. В случае большого количества параметров гораздо удобнее использовать именные placeholders. В данном случае можно передать данные в виде массива непосредственно в execute.
$STH = $DB->prepare("INSERT INTO table_name(name, addr, city) values(:name, :addr, :city)");          # массив значений 
$data = array('name'=>'Anton', 'addr'=>'Район булгакова', 'city' => 'China');          # выполняем запрос
$STH->execute($data);
Удобством использования именных placeholders является возможность вставки объектов напрямую в БД, если названия свойств совпадают с именами параметров. При преобразовании объекта в массив в execute, свойства считаются ключами массива.
class person{
          public $name;
          public $addr;
          public $city;
          function __construct($n,$a,$c){
                    $this->name = $n;
                    $this->addr = $a;
                    $this->city = $c; 
          }
}
$cathy = new person('Cathy','9 Dark and Twisty','Cardiff');          # выполняем запрос, преобразовав объект в массив
$STH = $DB->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)");
$STH->execute((array)$cathy);

Получение данных из MySQL с помощью метода fetch

Для получения данных используется метод fetch() с указанием вида получаемых данных.
PDO::FETCH_ASSOC: возвращает массив с названиями столбцов в виде ключей
PDO::FETCH_BOTH (по умолчанию): возвращает массив с индексами как в виде названий стобцов, так и их порядковых номеров
PDO::FETCH_BOUND: присваивает значения столбцов соответствующим переменным, заданным с помощью метода bindColumn()
PDO::FETCH_CLASS: присваивает значения столбцов соответствующим свойствам указанного класса. Если для какого-то столбца свойства нет, оно будет создано
PDO::FETCH_INTO: обновляет существующий экземпляр указанного класса
PDO::FETCH_LAZY: объединяет в себе PDO::FETCH_BOTH и PDO::FETCH_OBJ
PDO::FETCH_NUM: возвращает массив с ключами в виде порядковых номеров столбцов
PDO::FETCH_OBJ: возвращает анонимный объект со свойствами, соответствующими именам столбцов
На практике наиболее используемые: FETCH_ASSOC, FETCH_CLASS, и FETCH_OBJ. Вид данных можно установить через метод setFetchMode или непосредственно в самом методе fetch().
$STH->setFetchMode(PDO::FETCH_ASSOC);
При использовании FETCH_CLASS данные заносятся в экземпляры указанного класса. При этом значения назначаются свойствам объекта ДО вызова конструктора. Если свойства с именами, соответствующими названиям столбцов, не существуют, они будут созданы автоматически (с областью видимости public). Если полученные данные нуждаются в обязательной обработке сразу после их получения из БД, необходимо реализовать функцию обработки в конструкторе класса.
#	преобразуем значение адреса к секретному виду;
class secret_person{
          public $name, $addr, $city, $other_data;
          function __construct($other = ''){
                    $this->addr = preg_replace('/[a-z]/', 'x', $this->addr);
                    $this->other_data = $other;
          }
}
$STH = $DB->query('SELECT name, addr, city FROM table_name');
$STH->setFetchMode(PDO::FETCH_CLASS, 'secret_person');
while($obj = $STH->fetch()) echo $obj->addr;
В результате на экран будут выведены преобразованные данные. Чтобы выполнить конструктор класса до присваивания значений, необходимо указать параметр PDO::FETCH_PROPS_LATE. В результате, сохраненные данные преобразованы не будут.
$STH->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'secret_person');
При необходимости можно передать необходимые для конструктора аргументы
$STH->setFetchMode(PDO::FETCH_CLASS, 'secret_person', array('staff'));

Полезные методы PDO

В PDO есть весьма полезные методы, позволяющие оптимизировать и сократить код страницы. Следующий метод PDO позволяет получить идентификатор последней вставленной записи. Данный метод вызывается у объекта БД, а не у объекта выражения.
$DB->lastInsertId();
Для операций, не возвращающих никаких данных, кроме количества затронутых в них строк используется следующий метод:
$DBH->exec('DELETE FROM table_name WHERE 1');
Для получения количества затронутых в операции строк, используется следующий метод, применяемый к объекту выражения:
$rows_affected = $STH->rowCount();

Сложности с оператором LIKE в PDO

У оператора LIKE есть два собственных спецсимвола «_» и «%». Чтобы искать буквальное совпадение с этими символами, их необходимо прослэшить функцией addCslashes().
$data = addCslashes($data, '\%_');
В подготовленных выражениях плейсхолдер может заменять только строку или число. Ключевое слово, идентификатор, часть строки или набор строк через плейсхолдер подставить нельзя. Поэтому для LIKE необходимо подготавливать строку поиска целиком перед внедрением в запрос: 
$name = "%$name%";
$STH = $DB->prepare("SELECT*FROM table_name WHERE name LIKE ?");
$STH->execute(array($name));
$data = $STH->fetchAll();

Использование оператора LIMIT в PDO

При передаче данных напрямую в execute() в режиме PDO эмуляции, они форматируются как строки - эскейпятся и обрамляются кавычками. Поэтому LIMIT ?,? превращается в LIMIT '10', '10', что вызывает ошибку синтаксиса. Есть два варианта решения этой проблемы:
# отключение эмуляции
$DB->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
# биндить параметры, принудительно присваиваивая им тип PDO::PARAM_INT
$STH->bindParam(1, $limit_from, PDO::PARAM_INT);
$STH->bindParam(2, $per_page, PDO::PARAM_INT);

Использование оператора IN в PDO

Как уже говорилось выше, подставить набор данных на место одного плейсхолдера не получится. Поэтому для IN придется изворачиваться, динамически формируя две переменные: # набор знаков вопроса через запятую по числу элементов в IN()
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT*FROM table_name WHERE column IN($in)";          # массив данных для подстановки.
$arr = array(1,2,3);
Собственно говоря, это основные знания для полноценной работы с PDO. Назовем данную статью справочником понятий и рецептов работы с PDO, который я собирал и продолжаю собирать, по мере поступления новых проблем, со всего интернета в одном месте.
Оставить комментарий
Оставить комментарий
Оставайтесь на связи
Популярные статьи
Статьи по теме
Подписаться на рассылку
ПОДПИСАТЬСЯ
Поддержать проект

Если нравится наш блог, вы постоянно подчерпываете полезную информацию и делаете интересные открытия - поддержите наших авторов

Перевести
Оставить заявку
ОТПРАВИТЬ
+7 926 426 93 41anton.ross@yandex.ru
×
Добро пожаловать

Авторизуйтесь чтобы оставлять комментарии и добавлять фильмы в избранное

Войти
Войдите через:
Создать учетную запись
Восстановление пароля

Укажите email указанный при регистрации, на который будет отправлена инструкция по восстановлению пароля

Сбросить пароль
Добро пожаловать

Зарегистрируйтесь чтобы оставлять комментарии и добавлять фильмы в избранное

Зарегистрироваться

Регистрируясь на сайте Вы соглашаетесь с политикой конфиденциальности нашего сайта и даете согласие на обработку персональных данных

Войдите через:
У меня есть учетная запись
Смена пароля

Используйте код отправленный на указанную вами почту для смены пароля

Сменить пароль
×