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), имя базы данных (тест) и набор символов (utf8). Конечно, эти параметры тоже можно заменить переменными.
  • После $dsn приходит логин и пароль.
  • В $opt параметр - это массив, содержащий параметры конфигурации.

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

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

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

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

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

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

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

  • Используйте именованные заполнители только в том случае, если вам нужен сложный запрос или если у вас уже есть ассоциативный массив, ключи которого равны именам полей таблицы. В противном случае проще использовать обычные заполнители.
  • По возможности используйте "ленивую" привязку - передача данных в выполнение значительно сократит ваш код.
  • Если вы не знаете, нужно ли вам 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 Подготовленные операторы и LIKE

Сначала подготовьте полный литерал:

$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

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

$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

(из вспомогательной функции 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);