PDO (объекты данных PHP) - это уровень абстракции доступа к данным (интерфейс) для PHP. Он работает с большинством систем баз данных.

PDO обеспечивает уровень абстракции доступа к данным, что означает, что независимо от того, какую базу данных вы используете, вы используете одни и те же функции для выдачи запросов и извлечения данных. PDO не обеспечивает абстракцию базы данных; он не переписывает SQL и не эмулирует отсутствующие функции. Вы должны использовать полноценный слой абстракции, если вам нужно это средство.

Источник - https://php.net/manual/en/intro.pdo.php

соединение

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

$dsn = "mysql:host=localhost;dbname=test;charset=utf8";
$opt = array(
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$pdo = new PDO($dsn,'root','', $opt);

Давайте внимательнее посмотрим на этот код:

  • $dsn содержит драйвер базы данных (mysql), хост (localhost), имя базы данных (test) и набор символов (utf8). Конечно, эти параметры можно заменить и переменными.
  • После $dsn следует имя пользователя и пароль.
  • Параметр $opt представляет собой массив, содержащий параметры конфигурации.

Рекомендуется установить ERRMODE_EXCEPTION поскольку это позволит PDO генерировать исключения при ошибках; этот режим является наиболее надежным способом обработки ошибок PDO.
Установка ATTR_DEFAULT_FETCH_MODE также является хорошей идеей. Это избавляет вас от необходимости включать его в каждый выбор, делая код вашего приложения менее раздутым.

Есть много плохих примеров, говорящих вам try..catch каждую инструкцию PDO в try..catch - поэтому я должен сделать отдельную заметку:

НЕ используйте оператор try..catch только для обработки сообщения об ошибке. Непонятные исключения уже отлично подходят для этой цели, так как они будут обрабатывать ошибки PDO точно так же, как и другие ошибки PHP - так что вы можете определить поведение, используя настройки всего сайта.
Пользовательский обработчик исключений может быть добавлен позже, но это не обязательно. Особенно для новых пользователей рекомендуется использовать необработанные исключения, так как они чрезвычайно информативны, полезны и безопасны.
Больше информации...

Подготовленные заявления

Подготовленные заявления являются одной из основных причин использования PDO.
Как это работает, объясняется здесь: Как подготовленные операторы могут защитить от атак SQL-инъекций? Итак, здесь следует правила использования PDO:

  • Каждый динамический литерал данных должен быть представлен в запросе либо именем (:name), либо обычным заполнителем (?).
  • Каждый запрос должен быть выполнен в 3 (или 4) шага:
    • prepare() - подготовит запрос и создаст объект оператора.
    • bindValue()/ bindParam() - это необязательный шаг, поскольку переменные могут быть переданы непосредственно в execute().
    • execute() - фактически запустит запрос.
    • fetch* - вернет результат запроса в удобной форме.

Некоторые практические правила:

  • Используйте именованные заполнители, только если вам нужен сложный запрос или если у вас уже есть ассоциативный массив, ключи которого равны именам полей таблицы. В противном случае обычные заполнители проще в использовании.
  • По возможности используйте "ленивую" привязку - передача данных в execute значительно сократит ваш код.
  • Если вы не знаете, нужен ли вам bindValue() или bindParam(), перейдите к первому. bindValue() менее неоднозначен и имеет меньше побочных эффектов.

Итак, вот пример:

$id  = 1;
$stm = $pdo->prepare("SELECT name FROM table WHERE id=?");
$stm->execute(array($id));
$name = $stm->fetchColumn();

Получение результатов

У PDO есть несколько очень удобных методов для возврата результата запроса в разных форматах:

  • fetch() - метод извлечения общего назначения, похожий на mysql_fetch_array().
  • fetchAll() чтобы получить все строки без цикла while.
  • fetchColumn() чтобы получить единственное скалярное значение без получения массива.

fetchAll() - очень удобная функция, когда вы знакомы с отделением бизнес-логики от логики представления. Это позволяет вам сначала получить данные, а затем использовать их для отображения:

$stm = $pdo->prepare("SELECT id,name FROM news WHERE dt=curdate()");
$stm->execute();
$data = $stm->fetchAll();

Теперь у нас есть все новости в массиве $data и мы можем перейти к части презентации:

?>
<table>
<? foreach ($data as $row): ?>
  <tr>
    <td>
      <a href="news.php?<?=$row['id']?>">
        <?=htmlspecialchars($row['name'])?>
      </a>
    </td>
  </tr>
<? endforeach ?>

Сложные случаи

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

Вот несколько типичных случаев:

PDO Подготовленные заявления и как

Подготовьте полный буквальный сначала:

$name = "%$name%";
$stm  = $pdo->prepare("SELECT * FROM table WHERE name LIKE ?");
$stm->execute(array($name));
$data = $stm->fetchAll();

PDO Подготовленные заявления и LIMIT

В режиме эмуляции (который включен по умолчанию) PDO заменяет заполнители фактическими данными. А с "ленивым" связыванием (используя массив в execute()), PDO обрабатывает каждый параметр как строку. В результате готовится LIMIT?,? запрос становится LIMIT '10', '10' что является недопустимым синтаксисом, приводящим к сбою запроса.

Есть два решения:

  • Отключите эмуляцию (так как MySQL может правильно сортировать все заполнители).
  • Свяжите число явно и установите правильный тип (PDO :: PARAM_INT) для этой переменной.

Чтобы отключить эмуляцию, можно запустить этот код (или установить в массиве параметров подключения):

$conn->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );

Или связать эти переменные явно с параметром типа:

$stm = $pdo->prepare('SELECT * FROM table LIMIT ?, ?');
$stm->bindParam(1, $limit_from,PDO::PARAM_INT);
$stm->bindParam(2, $per_page,PDO::PARAM_INT);
$stm->execute();
$data = $stm->fetchAll();

ЗОП Подготовленные заявления и ВО

Невозможно заменить произвольную часть запроса, используя подготовленные операторы PDO. Для таких случаев, как оператор IN(), необходимо создать набор символов ? вручную и поместите их в запрос:

$arr = array(1,2,3);
$in  = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE column IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($arr);
$data = $stm->fetchAll();

PDO Подготовленные выписки и идентификаторы.

В PDO нет местозаполнителей для идентификаторов, таких как имена баз данных или таблиц, поэтому разработчик должен вручную отформатировать их. Чтобы правильно отформатировать идентификатор, следуйте этим двум правилам:

  • Вложите идентификатор в кавычки.
  • Побег задних внутри, удвоив их.

Код будет:

$table = "'".str_replace("'","''",$table)."'";

После такого форматирования безопасно вставить переменную $table в запрос.

Также важно всегда проверять динамические идентификаторы по списку допустимых значений. Вот краткий пример (из Как я могу предотвратить внедрение SQL в PHP?):

$orders  = array("name","price","qty"); //field names
$key     = array_search($_GET['sort'],$orders); // see if we have such a name
$orderby = $orders[$key]; //if not, first one will be set automatically. smart enuf :)
$query   = "SELECT * FROM 'table' ORDER BY $orderby"; //value is safe

другой пример можно найти ниже:

PDO Подготовленные операторы и запрос INSERT/UPDATE

(из вспомогательной функции Вставка/обновление с использованием PDO)
Обычный оператор запроса INSERT, подготовленный для PDO, состоит из 2-5 килобайт повторяющегося кода, причем каждое имя поля повторяется шесть-десять раз. Вместо этого нам нужна компактная вспомогательная функция для обработки переменного количества вставленных полей. Конечно, с помощью фейс-контроля для этих полей, чтобы разрешить в запросе только утвержденные поля.

Следующий код основан на предположении, что имена полей формы HTML равны именам полей таблицы SQL. Он также использует уникальную функцию MySQL, позволяющую использовать операторы SET для запросов INSERT и UPDATE:

function pdoSet($fields, &$values, $source = array()) {
  $set = '';
  $values = array();
  if (!$source) $source = &$_POST;
  foreach ($fields as $field) {
    if (isset($source[$field])) {
      $set.="'".str_replace("'","''",$field)."'". "=:$field, ";
      $values[$field] = $source[$field];
    }
  }
  return substr($set, 0, -2); 
}

Эта функция создаст правильную последовательность для оператора SET,

'field1'=:field1,'field2'=:field2

быть вставленным в запрос и массив $values для execute().
Можно использовать таким образом:

$allowed = array("name","surname","email"); // allowed fields
$sql = "INSERT INTO users SET ".pdoSet($allowed,$values);
$stm = $dbh->prepare($sql);
$stm->execute($values);

Или, для более сложного случая:

$allowed = array("name","surname","email","password"); // allowed fields
$_POST['password'] = MD5($_POST['login'].$_POST['password']);
$sql = "UPDATE users SET ".pdoSet($allowed,$values)." WHERE id = :id";
$stm = $dbh->prepare($sql);
$values["id"] = $_POST['id'];
$stm->execute($values);