Категория pdo. Настройка и использование PDO — расширения PHP Data Objects для работы с базами данных. Вот как я это делаю

  • Перевод

Множество PHP-разработчиков привыкли использовать для работы с базами данных расширения mysql и mysqli. Но с версии 5.1 в PHP существует более удобный способ - PHP Data Objects . Этот класс, сокращенно именуемый PDO, предоставляет методы для работы с объектами и prepared statements , которые заметно повысят вашу продуктивность!

Введение в PDO

«PDO – PHP Data Objects – это прослойка, которая предлагает универсальный способ работы с несколькими базами данных.»

Заботу об особенностях синтаксиса различных СУБД она оставляет разработчику, но делает процесс переключения между платформами гораздо менее болезненным. Нередко для этого требуется лишь изменить строку подключения к базе данных.


Эта статья написана для людей, которые пользуются mysql и mysqli, чтобы помочь им в переходе на более мощный и гибкий PDO.

Поддержка СУБД

Это расширение может поддерживать любую систему управления базами данных, для которой существует PDO-драйвер. На момент написания статьи доступны следующие драйвера:
  • PDO_CUBRID (CUBRID)
  • PDO_DBLIB (FreeTDS / Microsoft SQL Server / Sybase)
  • PDO_FIREBIRD (Firebird/Interbase 6)
  • PDO_IBM (IBM DB2)
  • PDO_INFORMIX (IBM Informix Dynamic Server)
  • PDO_MYSQL (MySQL 3.x/4.x/5.x)
  • PDO_OCI (Oracle Call Interface)
  • PDO_ODBC (ODBC v3 (IBM DB2, unixODBC and win32 ODBC))
  • PDO_PGSQL (PostgreSQL)
  • PDO_SQLITE (SQLite 3 and SQLite 2)
  • PDO_SQLSRV (Microsoft SQL Server)
  • PDO_4D (4D)
Впрочем, не все из них есть на вашем сервере. Увидеть список доступных драйверов можно так:
print_r(PDO::getAvailableDrivers());

Подключение

Способы подключения к разным СУБД могут незначительно отличаться. Ниже приведены примеры подключения к наиболее популярным из них. Можно заметить, что первые три имеют идентичный синтаксис, в отличие от SQLite.
try { # MS SQL Server и Sybase через PDO_DBLIB $DBH = new PDO("mssql:host=$host;dbname=$dbname", $user, $pass); $DBH = new PDO("sybase:host=$host;dbname=$dbname", $user, $pass); # MySQL через PDO_MYSQL $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass); # SQLite $DBH = new PDO("sqlite:my/database/path/database.db"); } catch(PDOException $e) { echo $e->getMessage(); }
Пожалуйста, обратите внимание на блок try/catch – всегда стоит оборачивать в него все свои PDO-операции и использовать механизм исключений (об этом чуть дальше).

$DBH расшифровывается как «database handle» и будет использоваться на протяжении всей статьи.

Закрыть любое подключение можно путем переопределения его переменной в null.
# закрывает подключение $DBH = null;
Больше информации по теме отличительных опций разных СУБД и методах подключения к ним можно найти на php.net .

Исключения и PDO

PDO умеет выбрасывать исключения при ошибках, поэтому все должно находиться в блоке try/catch. Сразу после создания подключения, PDO можно перевести в любой из трех режимов ошибок:
$DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Но стоит заметить, что ошибка при попытке соединения будет всегда вызывать исключение.

PDO::ERRMODE_SILENT

Это режим по умолчанию. Примерно то же самое вы, скорее всего, используете для отлавливания ошибок в расширениях mysql и mysqli. Следующие два режима больше подходят для DRY программирования.

PDO::ERRMODE_WARNING

Этот режим вызовет стандартный Warning и позволит скрипту продолжить выполнение. Удобен при отладке.

PDO::ERRMODE_EXCEPTION

В большинстве ситуаций этот тип контроля выполнения скрипта предпочтителен. Он выбрасывает исключение, что позволяет вам ловко обрабатывать ошибки и скрывать щепетильную информацию. Как, например, тут:
# подключаемся к базе данных try { $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass); $DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); # Черт! Набрал DELECT вместо SELECT! $DBH->prepare("DELECT name FROM people")->execute(); } catch(PDOException $e) { echo "Хьюстон, у нас проблемы."; file_put_contents("PDOErrors.txt", $e->getMessage(), FILE_APPEND); }
В SQL-выражении есть синтаксическая ошибка, которая вызовет исключение. Мы можем записать детали ошибки в лог-файл и человеческим языком намекнуть пользователю, что что-то случилось.

Insert и Update

Вставка новых и обновление существующих данных являются одними из наиболее частых операций с БД. В случае с PDO этот процесс обычно состоит из двух шагов. (В следующей секции все относится как к UPDATE, так и INSERT)


Тривиальный пример вставки новых данных:
# STH означает "Statement Handle" $STH = $DBH->prepare("INSERT INTO folks (first_name) values ("Cathy")"); $STH->execute();
Вообще-то можно сделать то же самое одним методом exec(), но двухшаговый способ дает все преимущества prepared statements. Они помогают в защите от SQL-инъекций, поэтому имеет смысл их использовать даже при однократном запросе.

Prepared Statements

Использование prepared statements укрепляет защиту от SQL-инъекций.

Prepared statement - это заранее скомпилированное SQL-выражение, которое может быть многократно выполнено путем отправки серверу лишь различных наборов данных. Дополнительным преимуществом является невозможность провести SQL-инъекцию через данные, используемые в placeholder’ах.

Ниже находятся три примера prepared statements.
# без placeholders - дверь SQL-инъекциям открыта! $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values ($name, $addr, $city)"); # безымянные placeholders $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)"); # именные placeholders $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)");
Первый пример здесь лишь для сравнения, его стоит избегать. Разница между безымянными и именными placeholder’ами в том, как вы будете передавать данные в prepared statements.

Безымянные placeholder’ы

# назначаем переменные каждому placeholder, с индексами от 1 до 3 $STH->bindParam(1, $name); $STH->bindParam(2, $addr); $STH->bindParam(3, $city); # вставляем одну строку $name = "Daniel" $addr = "1 Wicked Way"; $city = "Arlington Heights"; $STH->execute(); # вставляем еще одну строку, уже с другими данными $name = "Steve" $addr = "5 Circle Drive"; $city = "Schaumburg"; $STH->execute();
Здесь два шага. На первом мы назначаем всем placeholder’ам переменные (строки 2-4). Затем назначаем этим переменным значения и выполняем запрос. Чтобы послать новый набор данных, просто измените значения переменных и выполните запрос еще раз.

Если в вашем SQL-выражении много параметров, то назначать каждому по переменной весьма неудобно. В таких случаях можно хранить данные в массиве и передавать его:
# набор данных, которые мы будем вставлять $data = array("Cathy", "9 Dark and Twisty Road", "Cardiff"); $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)"); $STH->execute($data);
$data вставится на место первого placeholder’а, $data - на место второго, и т.д. Но будьте внимательны: если ваши индексы сбиты, это работать не будет.

Именные placeholder’ы

# первым аргументом является имя placeholder’а # его принято начинать с двоеточия # хотя работает и без них $STH->bindParam(":name", $name);
Здесь тоже можно передавать массив, но он должен быть ассоциативным. В роли ключей должны выступать, как можно догадаться, имена placeholder’ов.
# данные, которые мы вставляем $data = array("name" => "Cathy", "addr" => "9 Dark and Twisty", "city" => "Cardiff"); $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)"); $STH->execute($data);
Одним из удобств использования именных placeholder’ов является возможность вставки объектов напрямую в базу данных, если названия свойств совпадают с именами параметров. Вставку данных, к примеру, вы можете выполнить так:
# класс для простенького объекта 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 = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)"); $STH->execute((array)$cathy);
Преобразование объекта в массив при execute() приводит к тому, что свойства считаются ключами массива.

Выборка данных



Данные можно получить с помощью метода ->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. Чтобы задать формат данных, используется следующий синтаксис:
$STH->setFetchMode(PDO::FETCH_ASSOC);
Также можно задать его напрямую при вызове метода ->fetch().

FETCH_ASSOC

При этом формате создается ассоциативный массив с названиями столбцов в виде индексов. Он должен быть знаком тем, кто использует расширения mysql/mysqli.
# поскольку это обычный запрос без placeholder’ов, # можно сразу использовать метод query() $STH = $DBH->query("SELECT name, addr, city from folks"); # устанавливаем режим выборки $STH->setFetchMode(PDO::FETCH_ASSOC); while($row = $STH->fetch()) { echo $row["name"] . "\n"; echo $row["addr"] . "\n"; echo $row["city"] . "\n"; }
Цикл while() переберет весь результат запроса.

FETCH_OBJ

Данный тип получения данных создает экземпляр класса std для каждой строки.
# создаем запрос $STH = $DBH->query("SELECT name, addr, city from folks"); # выбираем режим выборки $STH->setFetchMode(PDO::FETCH_OBJ); # выводим результат while($row = $STH->fetch()) { echo $row->name . "\n"; echo $row->addr . "\n"; echo $row->city . "\n"; }

FETCH_CLASS

При использовании fetch_class данные заносятся в экземпляры указанного класса. При этом значения назначаются свойствам объекта ДО вызова конструктора. Если свойства с именами, соответствующими названиям столбцов, не существуют, они будут созданы автоматически (с областью видимости public).

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

Для примера возьмем ситуацию, когда вам нужно скрыть часть адреса проживания человека.
class secret_person { public $name; public $addr; public $city; public $other_data; function __construct($other = "") { $this->addr = preg_replace("//", "x", $this->addr); $this->other_data = $other; } }
При создании объекта все латинские буквы в нижнем регистре должны замениться на x. Проверим:
$STH = $DBH->query("SELECT name, addr, city from folks"); $STH->setFetchMode(PDO::FETCH_CLASS, "secret_person"); while($obj = $STH->fetch()) { echo $obj->addr; }
Если в базе данных адрес выглядит как ’5 Rosebud’, то на выходе получится ’5 Rxxxxxx’.

Конечно, иногда будет требоваться, чтобы конструктор вызывался ПЕРЕД присваиванием значений. PDO такое тоже позволяет.
$STH->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, "secret_person");
Теперь, когда вы дополнили предыдущий пример дополнительной опцией (PDO::FETCH_PROPS_LATE), адрес видоизменяться не будет, так как после записи значений ничего не происходит.

Наконец, при необходимости можно передавать конструктору аргументы прямо при создании объекта:
$STH->setFetchMode(PDO::FETCH_CLASS, "secret_person", array("stuff"));
Можно даже передавать разные аргументы каждому объекту:
$i = 0; while($rowObj = $STH->fetch(PDO::FETCH_CLASS, "secret_person", array($i))) { // что-то делаем $i++; }

Другие полезные методы

Хотя эта статья не может (и не пытается) охватить все аспекты работы с PDO (это огромный модуль!), оставить без упоминания следующие несколько функций нельзя.
$DBH->lastInsertId();
Метод ->lastInsertId() возвращает id последней вставленной записи. Стоит заметить, что он всегда вызывается у объекта базы данных (в статье он именуется $DBH), а не объекта с выражением ($STH).
$DBH->exec("DELETE FROM folks WHERE 1"); $DBH->exec("SET time_zone = "-8:00"");
Метод ->exec() используется для операций, которые не возвращают никаких данных, кроме количества затронутых ими записей.
$safe = $DBH->quote($unsafe);
Метод ->quote() ставит кавычки в строковых данных таким образом, что их становится безопасно использовать в запросах. Пригодится, если вы не используете prepared statements.
$rows_affected = $STH->rowCount();
Метод ->rowCount() возвращает количество записей, которые поучаствовали в операции. К сожалению, эта функция отказывалась работать с SELECT-запросами вплоть до PHP 5.1.6. Если обновить версию PHP не представляется возможным, количество записей можно получить так:
$sql = "SELECT COUNT(*) FROM folks"; if ($STH = $DBH->query($sql)) { # проверяем количество записей if ($STH->fetchColumn() > 0) { # делаем здесь полноценную выборку, потому что данные найдены! } else { # выводим сообщение о том, что удовлетворяющих запросу данных не найдено } }

Заключение

Надеюсь, этот материал поможет кому-то из вас осуществить миграцию с расширений mysql и mysqli.

Термин PDO является сокращением понятия PHP Data Objects . Как можно судить по названию, эта технология позволяет работать с содержимым базы данных через объекты.

Почему не myqli или mysql?

Чаще всего, в отношении новых технологий, встает вопрос их преимуществ перед старыми-добрыми и проверенными инструментами, а также, перевода на них текущих и старых проектов.

Объектная ориентированность PDO

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

Говоря о PHP , будем подразумевать современный объектно-ориентированный PHP , позволяющий писать универсальный код, удобный для тестирования и повторного использования.

Использование PDO позволяет вынести работу с базой данных на объектно-ориентированный уровень и улучшить переносимость кода. На самом деле, использование PDO не так сложно, как можно было бы подумать.

Абстракция

Представим, что мы уже продолжительное время разрабатываем приложение, с использованием MySQL . И вот, в один прекрасный момент, появляется необходимость заменить MySQL на PostgreSQL .

Как минимум, нам придется заменить все вызовы mysqli_connect() (mysql_connect()) на pg_connect() и, по аналогии, другие функции, используемые для запроса и обработки данных.

При использовании PDO , мы ограничимся изменением нескольких параметров в файлах конфигурации.

Связывание параметров

Использование связанных параметров предоставляет большую гибкость в составлении запросов и позволяет улучшить защиту от SQL инъекций.

Получение данных в виде объектов

Те, кто уже использует ORM (object-relational mapping — объектно-реляционное отображение данных), например, Doctrine , знают удобство представления данных из таблиц БД в виде объектов. PDO позволяет получать данные в виде объектов и без использования ORM .

Расширение mysql больше не поддерживается

Поддержка расширения mysql окончательно удалена из нового PHP 7 . Если вы планируете переносить проект на новую версию PHP , уже сейчас следует использовать в нем, как минимум, mysqli. Конечно же, лучше начинать использовать PDO , если вы еще не сделали этого.

Мне кажется, что этих причин достаточно для склонения весов в сторону использования PDO . Тем более, не нужно ничего дополнительно устанавливать.

Проверяем наличие PDO в системе

Версии PHP 5.5 и выше, чаще всего, уже содержать расширение для работы с PDO . Для проверки достаточно выполнить в консоли простую команду:

php -i | grep "pdo"

Теперь откроем его в любом браузере и найдем нужные данные поиском по строке PDO .

Знакомимся с PDO

Процесс работы с PDO не слишком отличается от традиционного. В общем случае, процесс использования PDO выглядит так:

  1. Подключение к базе данных;
  2. По необходимости, подготовка запроса и связывание параметров;
  3. Выполнение запроса.

Подключение к базе данных

Для подключения к базе данных нужно создать новый объект PDO и передать ему имя источника данных, так же известного как DSN .

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

Для MySQL , подключение выполняется так:

$connection = new PDO("mysql:host=localhost;dbname=mydb;charset=utf8", "root", "root");

$connection = new PDO ("mysql:host=localhost;dbname=mydb;charset=utf8" , "root" , "root" ) ;

В данном случае, DSN содержит имя драйвера mysql , указание хоста (возможен формат host=ИМЯ_ХОСТА:ПОРТ ), имя базы данных, кодировка, имя пользователя MySQL и его пароль.

Запросы

В отличие от mysqli_query() , в PDO есть два типа запросов:

  • Возвращающие результат (select, show );
  • Не возвращающие результат (insert , detele и другие).

Первым делом, рассмотрим второй вариант.

Выполнение запросов

Рассмотрим пример выполнения запроса на примере insert .

$connection->exec("INSERT INTO users VALUES (1, "somevalue"");

$connection -> exec () ;

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

$affectedRows = $connection->exec("INSERT INTO users VALUES (1, "somevalue""); echo $affectedRows;

$affectedRows = $connection -> exec ("INSERT INTO users VALUES (1, "somevalue"" ) ;

echo $affectedRows ;

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

В случае использования mysqli_query () , код мог бы быть следующим.

$result = mysql_query("SELECT * FROM users"); while($row = mysql_fetch_assoc($result)) { echo $row["id"] . " " . $row["name"]; }

$result = mysql_query ("SELECT * FROM users" ) ;

while ($row = mysql_fetch_assoc ($result ) ) {

Для PDO , код будет проще и лаконичнее.

foreach($connection->query("SELECT * FROM users") as $row) { echo $row["id"] . " " . $row["name"]; }

foreach ($connection -> query ("SELECT * FROM users" ) as $row ) {

echo $row [ "id" ] . " " . $row [ "name" ] ;

Режимы получения данных

Как и в mysqli , PDO позволяет получать данные в разных режимах. Для определения режима, класс PDO содержит соответствующие константы.

  • PDO:: FETCH_ASSOC — возвращает массив, индексированный по имени столбца в таблице базы данных;
  • PDO:: FETCH_NUM — возвращает массив, индексированный по номеру столбца;
  • PDO:: FETCH_OBJ — возвращает анонимный объект с именами свойств, соответствующими именам столбцов. Например, $row->id будет содержать значение из столбца id.
  • PDO:: FETCH_CLASS — возвращает новый экземпляр класса, со значениями свойств, соответствующими данным из строки таблицы. В случае, если указан параметр PDO:: FETCH_CLASSTYPE (например PDO:: FETCH_CLASS | PDO:: FETCH_CLASSTYPE ), имя класса будет определено из значения первого столбца.

Примечание : это не полный список, все возможные константы и варианты их комбинации доступны в документации .

Пример получения ассоциативного массива:

$statement = $connection->query("SELECT * FROM users"); while($row = $statement->fetch(PDO::FETCH_ASSOC)) { echo $row["id"] . " " . $row["name"]; }

$statement = $connection ->

while ($row = $statement -> fetch (PDO:: FETCH_ASSOC ) ) {

echo $row [ "id" ] . " " . $row [ "name" ] ;

Примечание : Рекомендуется всегда указывать режим выборки, так как режим PDO:: FETCH_BOTH потребует вдвое больше памяти — фактически, будут созданы два массива, ассоциативный и обычный.

Рассмотрим использование режима выборки PDO:: FETCH_CLASS . Создадим класс User :

class User { protected $id; protected $name; public function getId() { return $this->id; } public function setId($id) { $this->id = $id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } }

class User

protected $id ;

protected $name ;

public function getId ()

return $this -> id ;

public function setId ($id )

$this -> id = $id ;

public function getName ()

return $this -> name ;

public function setName ($name )

$this -> name = $name ;

Теперь выберем данные и отобразим данные при помощи методов класса:

$statement = $connection->query("SELECT * FROM users"); while($row = $statement->fetch(PDO::FETCH_CLASS, "User")) { echo $row->getId() . " " . $row->getName(); }

$statement = $connection -> query ("SELECT * FROM users" ) ;

while ($row = $statement -> fetch (PDO:: FETCH_CLASS , "User" ) ) {

echo $row -> getId () . " " . $row -> getName () ;

Подготовленные запросы и связывание параметров

Для понимания сути и всех преимуществ связывания параметров нужно более подробно рассмотреть механизмы PDO . При вызове $statement -> query () в коде выше, PDO подготовит запрос, выполнит его и вернет результат.

При вызове $connection -> prepare () создается подготовленный запрос. Подготовленные запросы — это способность системы управления базами данных получить шаблон запроса, скомпилировать его и выполнить после получения значений переменных, использованных в шаблоне. Похожим образом работают шаблонизаторы Smarty и Twig .

При вызове $statement -> execute () передаются значения для подстановки в шаблон запроса и СУБД выполняет запрос. Это действие аналогично вызову функции шаблонизатора render () .

Пример использования подготовленных запросов в PHP PDO :

В коде выше подготовлен запрос выборки записи с полем id равным значению, которое будет подставлено вместо : id . На данном этапе СУБД выполнит анализ и компиляцию запроса, возможно с использованием кеширования (зависит от настроек).

Теперь нужно передать недостающий параметр и выполнить запрос:

$id = 5; $statement->execute([ ":id" => $id ]);

Преимущества использования связанных параметров

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

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

Вместо этого, теперь целесообразно делать так:

Можно, даже, еще укоротить код, используя нумерованные параметры вместо именованных:

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

$numberOfUsers = $connection->query("SELECT COUNT(*) FROM users")->fetchColumn(); $users = ; $statement = $connection->prepare("SELECT * FROM users WHERE id = ? LIMIT 1"); for ($i = 1; $i <= 5; $i++) { $id = rand(1, $numberOfUsers); $users = $statement->execute([$id])->fetch(PDO::FETCH_OBJ); }

$numberOfUsers = $connection -> query ("SELECT COUNT(*) FROM users" ) -> fetchColumn () ;

$users = ;

for ($i = 1 ; $i <= 5 ; $i ++ ) {

$id = rand (1 , $numberOfUsers ) ;

$users = $statement -> execute ([ $id ] ) -> fetch (PDO:: FETCH_OBJ ) ;

При вызове метода prepare () , СУБД проведет анализ и скомпилирует запрос, при необходимости использует кеширование. Позже, в цикле for , происходит только выборка данных с указанным параметром. Такой подход позволяет быстрее получить данные, уменьшив время работы приложения.

При получении общего количества пользователей в базе данных был использован метод fetchColumn () . Этот метод позволяет получить значение одного столбца и является полезным при получении скалярных значений, таких как количество, сумма, максимально или минимальное значения.

Связанные значения и оператор IN

Часто, при начале работы с PDO , возникают трудности с оператором IN . Например, представим, что пользователь вводит несколько имен, разделенных запятыми. Пользовательский ввод хранится в переменной $names .

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

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

Что такое PDO?

PDO(PHP Data Objects) - это просто интерфейс , позволяющий нам абстрагироваться от конкретной базы данных. Лучше всего показать на примере.

Mysql_connect($host, $user, $pass); // MySQL
mysql_select_db($db);

Sqlite_open($db); // sqlite

Pg_connect("host=$host, dbname=$db, user=$user, password=$pass"); // PostgreSQL

В коде выше представлены способы для подключения к трём разным базам данных: MySQL , sqlite и PostgreSQL . Как видите, функции у каждой БД отличаются.

То же самое с другими действиями. Например, выборка данных из БД.

$sql = "INSERT INTO(name, pass) VALUES($name, $pass)";

Mysql_query($sql); // MySQL
sqlite_query($sql); // sqlite
pg_query($sql); // PostgreSQL

Зачем нужен PDO?

Представим, что у нас есть огромная база данных PostgreSQL , и мы решили сменить её на MySQL . Нам придётся переписывать очень много кода, и, скорее всего, без ошибок не обойдётся. Чтобы решить эту проблему и существует PDO , позволяющий нам не зависеть от конкретной базы.

Давайте рассмотрим, как мы теперь можем подключиться.

$db = new PDO("mysql:host=$host;dbname=$db", $user, $pass); // MySQL
$db = new PDO("sqlite:host=$host;dbname=$db", $user, $pass); // sqlite
$db = new PDO("pgsql:host=$host;dbname=$db", $user, $pass); // PostgreSQL

Как видно из кода выше, в этих трёх подключениях меняется только строчка с названием БД, а остальное всё то же самое.

Чтобы что-нибудь выбрать, мы можем написать так:

$db->exec($sql);

Всё! Запрос выполнится независимо от того, какая у нас база данных.

Поддержка

PDO доступен с PHP 5.1 . Чтобы мы могли "забыть", какую базу данных мы используем, за нас всё делают их драйверы . Чтобы их активировать, зайдите в файл php.ini и найдите там строчки, которые начинаются на extension=php_pdo_ , а затем идёт название базы данных, и раскоментируйте их.

На этом всё для вступительной статьи, а в следующей мы уже начнём разбираться, как использовать PDO .

Фреймворк Bootstrap: быстрая адаптивная вёрстка

Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.

Верстайте на заказ и получайте деньги.

Бесплатный курс "Сайт на WordPress"

Хотите освоить CMS WordPress?

Получите уроки по дизайну и верстке сайта на WordPress.

Научитесь работать с темами и нарезать макет.

Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!

*Наведите курсор мыши для приостановки прокрутки.

Назад Вперед

Основы работы с расширением PDO

Сегодня мы с вами разберём очень интересную тему - основы работы с расширением PDO для PHP.

PDO (PHP Data Objects) - это просто некий интерфейс, который позволяет работать с различными базами данных без учета их специфики. С помощью PDO мы можем легко переключаться между разными базами данных и управлять ими. Чтобы стало понятнее, давайте разберём пример.

Как мы должны были подключаться раньше к базе MySQL ?

Mysql_connect($host, $user, $password); mysql_select_db($db);

Чтобы подключиться к SQLite мы должны были написать так:

Sqlite_open($db);

Если нам нужна база данных PostgreSQL , то надо написать так:

Pg_connect("host=$host, dbname=$db, user=$user, password=$password");

Не очень-то удобно, верно? Получается, что если мы захотим сменить базу данных, то нам придется переделывать много кода. И вот, чтобы это исправить, появилось специальное PHP-расширение - PDO .

Давайте посмотрим, как мы теперь можем подключиться к базе:

$db = new PDO("mysql:host=$host;dbname=$db", $user, $password);

$db = new PDO("sqlite:$db);

PostgreSQL:

$db = new PDO("pgsql:host=$host;dbname=$db", $user, $password);

Как видите, всё то же самое, кроме строки подключения. Это единственное различие.


Теперь давайте рассмотрим, как раньше мы были должны выполнять запросы:

$sql = "INSERT INTO(name, email) VALUES($name, $email)"; // MySQL mysql_query($sql); // SQLite sqlite_query($sql); // PostgreSQL pg_query($sql);

Теперь же мы можем абстрагироваться от этого:

// PDO $result = $db->exec($sql);

Всё! Наш запрос будет выполнен независимо от того, какую БД мы используем , а в переменную result попадёт количество затронутых строк.

Однако выбрать что-то из базы данных таким способом мы не сможем. Для выборки нам нужно использовать не exec , а query .

$sql = "SELECT name FROM users"; $result = $db->query($sql);

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

$sql = "SELECT * FROM users WHERE name = $name"; $name = $_POST["name"]; // MySQL $name = mysql_real_escape_string($name); // SQLite $name = sqlite_escape_string($name); // PostgreSQL $name = pg_escape_string($name);

Теперь же нам не нужно этого делать. PDO сделает всё за нас.

$name = $db->quote($name); $result = $db->query($sql);

PDO сам всё проверит и обработает переданные данные. Круто?:) Дальше ещё круче! Продолжим.

Как мы раньше преобразовывали результат в массив? Рассмотрим на примере базы MySQL .

$result = mysql_query($sql); // Так $row = mysql_fetch_assoc($result); // Или так... $row = mysql_fetch_array($result, FETCH_ASSOC);

Также, как и ассоциативный, мы могли получить и нумерованный массив. Теперь рассмотрим как это делается в PDO :

$stmt = $db->query($sql); // Ассоциативный $result = $stmt->FETCH(PDO::FETCH_ASSOC); // Нумерованный $result = $stmt->FETCH(PDO::FETCH_NUM); // Оба типа массивов одновременно $result = $stmt->FETCH(PDO::FETCH_BOTH); // Объект $result = $stmt->FETCH(PDO::FETCH_OBJ);

Использовать это также очень просто:

// Ассоциативный echo $result["name"]; // Нумерованный echo $result; // Объект echo $result->name;

Для "ленивых" есть такая вещь:

$stmt = $db->query($sql); $result = $stmt->FETCH(PDO::FETCH_LAZY);

Он возвращает сразу все 3 типа. Т.е. это FETCH_BOTH и FETCH_OBJ вместе. Как вы уже догадались, после этого доступ к данным можно получить любым из трех способов:

Echo $result->name; echo $result["name"]; echo $result;

Однако Fetch возвращает только одну запись, поэтому, если мы хотим получить все записи, то надо использовать FetchAll .

$stmt = $db->query("SELECT * FROM users"); $result = $stmt->FetchAll(PDO::FETCH_ASSOC); foreach($result as $user) { echo $user["name"]."
"; }

Но есть ещё одна классная штука, связанная с Fetch . С ее помощью мы можем заполнить наш класс данными из БД автоматически .

Class User { public $login; public $id; public function showInfo() { echo "".$this->id.""." : ".$this->login."
"; } } $db = new PDO("mysql:host=localhost;dbname=test", "root", ""); $stmt = $db->query("SELECT * FROM `users`"); $result = $stmt->fetchAll(PDO::FETCH_CLASS, "User"); foreach($result as $user) { $user->showInfo(); }

Как видите всё очень просто. Нам нужно просто указать константу FETCH_CLASS и через запятую в кавычках название класса, куда будут вставлены данные.

Потом перебираем в цикле объект и выводим нужную нам информацию.
Внимание! Названия свойств в классе должны совпадать с названиями полей в базе данных.

Помимо всего прочего, мы можем создавать так называемые подготовленные запросы . В чём их плюсы?

1. Мы можем один раз подготовить запрос, после чего запускать его столько раз, сколько нам нужно. Причём как с такими же, так и с другими параметрами.

Когда запрос подготовлен, СУБД анализирует его, компилирует и оптимизирует план его выполнения. В случае сложных запросов, время на выполнение будет ощутимо, если мы запускаем его с разными параметрами. В случае с подготовленными запросами это делается один раз и, следовательно, времени тратится меньше.

2. Параметры подготовленного запроса не требуется экранировать кавычками, драйвер делает это автоматически. Если в приложении используются только подготовленные запросы, то SQL-иньекции почти невозможны.

PDO может эмулировать подготовленные запросы , если они не поддерживаются драйвером. Теперь, давайте рассмотрим, как же их использовать?

$stmt = $db->prepare("INSERT INTO users (name, login) VALUES (:name, :login)"); $stmt->bindParam(":name", $name); $stmt->bindParam(":login", $login); // Вставим одну строку с такими значениями $name = "vasya"; $login = "vasya123"; $stmt->execute(); // Теперь другую строку с другими значениями $name = "petya"; $login = "petya123"; $stmt->execute();

Метод bindParam позволяет нам установить параметры. Думаю, тут всё понятно. Сначала там, где хотим, чтобы были вставлены данные, пишем такую строчку ":имя ". А затем указываем, откуда они будут браться. В данном случае они будут браться из переменных name и login .

Теперь мы можем использовать этот запрос с разными параметрами сколько угодно раз, а чтобы его выполнить, нужно вызвать метод execute . Это были именованные параметры. Также есть и не именованные .

$stmt = $db->prepare("INSERT INTO users (name, login) VALUES (?, ?)"); // Данные из переменной name будут вставлены вместо первого знака вопроса $stmt->bindParam(1, $name); // Данные из переменной login будут вставлены вместо второго знака вопроса $stmt->bindParam(2, $login); // Вставим одну строку с такими значениями $name = "vasya"; $login = "vasya123"; $stmt->execute(); // Теперь другую строку с другими значениями $name = "petya"; $login = "petya123"; $stmt->execute();

Следующий момент - как нам отлавливать ошибки?

Для этого есть класс PDOException . Я рекомендую все ваши запросы писать в блоке try-catch .

Try { $db = new PDO("myql:host=localhost;dbname=test", "root", ""); $stmt = $db->query("SELECT * FROM users"); $result = $stmt->fetch(PDO::FETCH_ASSOC); echo $result["login"]; } catch(PDOException $e) { echo "Ошибка: ".$e->getMessage()."
"; echo "На линии: ".$e->getLine(); }

Здесь мы допустили ошибку и написали myql вместо mysql . И класс PDOException нам об этом напишет.

У него несколько методов, но самые часто используемые это getMessage() , который возвращает нам текст ошибки и getLine() , который возвращает номер строки, на которой допущена ошибка.

Ну и напоследок поговорим о транзакциях . Сначала приведу код.

Try { $db = new PDO("mysql:host=localhost;dbname=test", "root", ""); $db->beginTransaction(); $stmt = $db->exec("INSERT INTO `users`(`login`) VALUES("login1")"); $stmt = $db->exec("INSERT INTO `users`(`login`) VALUES("login2")"); $stmt = $db->exec("INSERT INTO `users`(`login`) VALUES("login3")"); $db->commit(); } catch(PDOException $e) { $db->rollBack(); }

Здесь мы начинаем транзакцию с помощью метода beginTransaction() . Дальше идёт какой-то код запросов. Затем мы вызываем метод commit() , чтобы подтвердить наши изменения. Если же что-то пошло не так, то в блоке catch мы вызываем метод rollBack() , который вернёт все наши данные в предыдущее состояние.

"А зачем собственно нужны эти транзакции?" - спросите вы. Чтобы ответить на этот вопрос, рассмотрим пример, который я привёл выше. Там вы вставляем в поле "логин" значение login1, login2, login3 .

Представим, что после того, как вставились login1 и login2 , произошла какая-то ошибка. Получится, что эти данные вставлены, а login3 - нет. Во многих случаях это недопустимо и нарушит работу приложения в будущем.

Как раз для предотвращения таких ситуаций и нужны транзакции. Если наш скрипт дал сбой, то метод rollBack() вернёт всё в первоначальный вид. Т.е. login1 и login2 также не будут вставлены. Давайте сэмулируем эту ошибку.

Try { $db = new PDO("mysql:host=localhost;dbname=test", "root", ""); $db->beginTransaction(); $stmt = $db->exec("INSERT INTO `users`(`login`) VALUES("login1")"); $stmt = $db->exec("INSERT INTO `users`(`login`) VALUES("login2")"); exit("error"); $stmt = $db->exec("INSERT INTO `users`(`login`) VALUES("login3")"); $db->commit(); } catch(PDOException $e) { $db->rollBack(); }

После вставки login1 и login2 мы выходим из скрипта с помощью функции exit() . У нас выбрасывается исключение, мы попадаем в блок catch , а там мы возвращаем всё в первоначальный вид. Теперь, если мы посмотрим в базу данных, то не увидим там login1 и login2 .

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

Материал подготовил Владислав Андреев специально для сайта сайт

P.S. Хотите двигаться дальше в освоении PHP и ООП? Обратите внимание на премиум-уроки по различным аспектам сайтостроения, включая программирование на PHP, а также на бесплатный курс по созданию своей CMS-системы на PHP с нуля с использованием ООП:

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!


Introduction

Optionally, the --with-mysql-sock[=DIR] sets to location to the MySQL unix socket pointer for all MySQL extensions, including PDO_MYSQL. If unspecified, the default locations are searched.

Optionally, the --with-zlib-dir[=DIR] is used to set the path to the libz install prefix.

$ ./configure --with-pdo-mysql --with-mysql-sock=/var/mysql/mysql.sock

Changelog
Version Description
5.4.0 mysqlnd became the default MySQL library when compiling PDO_MYSQL. Previously, libmysqlclient was the default MySQL library.
5.4.0 MySQL client libraries 4.1 and below are no longer supported.
5.3.9 Added SSL support with mysqlnd and OpenSSL.
5.3.7 Added SSL support with libmysqlclient and OpenSSL.

Predefined Constants

The constants below are defined by this driver, and will only be available when the extension has been either compiled into PHP or dynamically loaded at runtime. In addition, these driver-specific constants should only be used if you are using this driver. Using driver-specific attributes with another driver may result in unexpected behaviour. PDO::getAttribute() may be used to obtain the PDO::ATTR_DRIVER_NAME attribute to check the driver, if your code can run against multiple drivers.

PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (integer ) If this attribute is set to TRUE on a PDOStatement , the MySQL driver will use the buffered versions of the MySQL API. If you"re writing portable code, you should use PDOStatement::fetchAll() instead.

Example #1 Forcing queries to be buffered in mysql

if ($db -> getAttribute (PDO :: ATTR_DRIVER_NAME ) == "mysql" ) {
$stmt = $db -> prepare ("select * from foo" ,
array(PDO :: MYSQL_ATTR_USE_BUFFERED_QUERY => true ));
} else {
die("my application only works with mysql; I should use \$stmt->fetchAll() instead" );
}
?>

PDO::MYSQL_ATTR_LOCAL_INFILE (integer )

Enable LOAD LOCAL INFILE .

PDO::MYSQL_ATTR_INIT_COMMAND (integer )

Command to execute when connecting to the MySQL server. Will automatically be re-executed when reconnecting.

Note, this constant can only be used in the driver_options array when constructing a new database handle.

PDO::MYSQL_ATTR_READ_DEFAULT_FILE (integer )

Read options from the named option file instead of from my.cnf . This option is not available if mysqlnd is used, because mysqlnd does not read the mysql configuration files.

PDO::MYSQL_ATTR_READ_DEFAULT_GROUP (integer )

Read options from the named group from my.cnf or the file specified with MYSQL_READ_DEFAULT_FILE . This option is not available if mysqlnd is used, because mysqlnd does not read the mysql configuration files.

PDO::MYSQL_ATTR_MAX_BUFFER_SIZE (integer )

Maximum buffer size. Defaults to 1 MiB. This constant is not supported when compiled against mysqlnd.

PDO::MYSQL_ATTR_DIRECT_QUERY (integer )

Perform direct queries, don"t use prepared statements.

PDO::MYSQL_ATTR_FOUND_ROWS (integer )

Return the number of found (matched) rows, not the number of changed rows.

PDO::MYSQL_ATTR_IGNORE_SPACE (integer )

Permit spaces after function names. Makes all functions names reserved words.

PDO::MYSQL_ATTR_COMPRESS (integer )

Enable network communication compression. This is also supported when compiled against mysqlnd as of PHP 5.3.11.

PDO::MYSQL_ATTR_SSL_CA (integer )

The file path to the SSL certificate authority.

This exists as of PHP 5.3.7.

PDO::MYSQL_ATTR_SSL_CAPATH (integer )

The file path to the directory that contains the trusted SSL CA certificates, which are stored in PEM format.

This exists as of PHP 5.3.7.

PDO::MYSQL_ATTR_SSL_CERT (integer )

The file path to the SSL certificate.

This exists as of PHP 5.3.7.

PDO::MYSQL_ATTR_SSL_CIPHER (integer )

A list of one or more permissible ciphers to use for SSL encryption, in a format understood by OpenSSL. For example: DHE-RSA-AES256-SHA:AES128-SHA

This exists as of PHP 5.3.7.

PDO::MYSQL_ATTR_SSL_KEY (integer )

The file path to the SSL key.

This exists as of PHP 5.3.7.

PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT (integer )

Provides a way to disable verification of the server SSL certificate.

This exists as of PHP 7.0.18 and PHP 7.1.4.

PDO::MYSQL_ATTR_MULTI_STATEMENTS (integer )

Disables multi query execution in both PDO::prepare() and PDO::query() when set to FALSE .

Note, this constant can only be used in the driver_options array when constructing a new database handle.

This exists as of PHP 5.5.21 and PHP 5.6.5.

Runtime Configuration

The behaviour of these functions is affected by settings in php.ini .

PDO_MYSQL Configuration Options
Name Default Changeable
pdo_mysql.default_socket "/tmp/mysql.sock" PHP_INI_SYSTEM
pdo_mysql.debug NULL PHP_INI_SYSTEM
For further details and definitions of the PHP_INI_* modes, see the .

Here"s a short explanation of the configuration directives.

Sets a Unix domain socket. This value can either be set at compile time if a domain socket is found at configure. This ini setting is Unix only.

Pdo_mysql.debug boolean

Enables debugging for PDO_MYSQL. This setting is only available when PDO_MYSQL is compiled against mysqlnd and in PDO debug mode.

Table of Contents

  • PDO_MYSQL DSN - Connecting to MySQL databases

Than install them

rpm -Uvh remi-release-26.rpm
rpm -Uvh epel-release-6-8.noarch.rpm

Know you can use remi repository to gest php-pdo and php-mysql.

yum --enablerepo=remi install php-pdo
yum --enablerepo=remi install php-mysql

Restart the Apache

systemctl stop httpd
systemctl start httpd

Good to go!

10 years ago

SQLSTATE: General error: 2014 Cannot execute queries while other unbuffered queries are active. ...

This one can be a royal pain to deal with. Never stack statements to be executed in one go. Nobody ever mentions this possibility in all the posts I"ve seen dealing with this error.

This example is a Zend Framework example but the theory is the same.

$sql = <<<____SQL

`tid` int(11) NOT NULL,

`tgen` datetime NOT NULL,
`tterm` datetime,

`rqid` int(11) NOT NULL,
`rqtid` int(11) NOT NULL,
`rqsid` int(11) NOT NULL,
`rqdate` datetime NOT NULL,
`rssid` int(11) NOT NULL,
`rsdate` datetime,
`rscode` tinyint(1)

`rqid` int(5) NOT NULL,

`sid` int(11) NOT NULL,
`rlsid` int(11) NOT NULL,
`dcode` varchar(5) NOT NULL

____SQL;
$result = $this -> db -> getConnection ()-> exec ($sql );
?>
This will run fine but PDO will balk with the "unbuffered" error if you follow this with another query.

$sql = <<<____SQL
CREATE TABLE IF NOT EXISTS `ticket_hist` (
`tid` int(11) NOT NULL,
`trqform` varchar(40) NOT NULL,
`trsform` varchar(40) NOT NULL,
`tgen` datetime NOT NULL,
`tterm` datetime,
`tstatus` tinyint(1) NOT NULL
) ENGINE=ARCHIVE COMMENT="ticket archive";
____SQL;
$result = $this -> db -> getConnection ()-> exec ($sql );

$sql = <<<____SQL
CREATE TABLE IF NOT EXISTS `request_hist` (
`rqid` int(11) NOT NULL,
`rqtid` int(11) NOT NULL,
`rqsid` int(11) NOT NULL,
`rqdate` datetime NOT NULL,
`rqcode` tinyint(1) NOT NULL,
`rssid` int(11) NOT NULL,
`rsdate` datetime,
`rscode` tinyint(1)
) ENGINE=ARCHIVE COMMENT="request archive";
____SQL;
$result = $this -> db -> getConnection ()-> exec ($sql );

$sql = <<<____SQL
CREATE TABLE IF NOT EXISTS `relay_hist` (
`rqid` int(5) NOT NULL,
`sdesc` varchar(40) NOT NULL,
`rqemail` varchar(40) NOT NULL,
`sid` int(11) NOT NULL,
`rlsid` int(11) NOT NULL,
`dcode` varchar(5) NOT NULL
) ENGINE=ARCHIVE COMMENT="relay archive";
____SQL;
$result = $this -> db -> getConnection ()-> exec ($sql );
?>
Chopping it into individual queries fixes the problem.