Описание тега pdo
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);