PDO является частью php начиная с версии 5.1.0, вышедшей в ноябре 2005. PHP Data Objects (PDO) обеспечивает методы для подготовки выражений и работы с объектами баз данных. Он не зависит от специфического синтаксиса базы данных и позволяет легко переключаться на другой тип баз данных и платформу простым изменением строки подключения в большинстве случаев.
(По материалам

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

  • 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 и win32 ODBC) )
  • PDO_PGSQL ( PostgreSQL )
  • PDO_SQLITE ( SQLite 3 и SQLite 2 )
  • PDO_4D ( 4D )

Для работы системы достаточно установить только те драйвера, которые действительно нужны. Получить список драйверов, доступных в системе можно следующим образом:
print_r(PDO::getAvailableDrivers());

Подключение

Различные базы данных могут иметь немного различающиеся методы подключения. Ниже показаны методы подключения к нескольким популярным баз данных. Можно заметить, что первые три идентичны друг другу, и только SQLite имеет специфический синтаксис.Строка подключения к базе данных
try {
$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 в блок try/catch и использовать механизм исключений. Обычно выполняется только одно подключение, в нашем примере показаны несколько подключений для отображения синтаксиса. $DBH содержит дескриптор базы данных и будет использоваться на протяжении всего нашего урока.

сли же в SQL выражении вы допустили ошибку, в PDOесть специальные функции:

errorCode() – возвращает номер ошибки, и

errorInfo() – возвращает массив, в котором, как номер ошибки, так и текст описания

Вы можете закрыть любое соединение установкой дескриптора в null.
# Закрываем соединение $DBH = null;
Запросы непосредственно можно делать двумя функциями:

exec() и query()

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

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 );
Вне зависимости от установленного уровня контроля ошибка соединения всегда вызывает исключение и поэтому всегда должна быть заключена в блок try/catch.

PDO::ERRMODE_SILENT

Уровень контроля ошибок, устанавляваемы по умолчанию. На этом уровене ошибки генерируются по такому же принципу, как в расширениях mysql или mysqli. Два других уровня контроля ошибок более подходят для стиля програмирования в стиле DRY (Don’t Repeat Youself — не повторяй сам себя).

PDO::ERRMODE_WARNING

На данном уровне контроля ошибок генеррируются стандартные предупреждения PHP, при это м программа может продолжать выполение. Данный уровень удобен для отладки.

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’);
}
catch(PDOException $e) {
echo » Извините. Но операци не может быть выполнена.»;
file_put_contents(‘PDOErrors.txt’, $e->getMessage(), FILE_APPEND);
}
Здесь сделана преднамеренная ошибка в выражении SELECT. Это вызовет исключение. Исключение отправит описание ошибки в log файл и выдаст сообщение пользователю.

Вставка и обновление данных

Вставка новых данных или обновление существующих — одна из наиболее часто используемых общих операций баз данных. При использовании PDO, она раскладывается на два этапа. Все, что описана в данной главе применимо и к обоим операциям UPDATE и INSERT.
Обновление данных
Здесь приведен пример наиболее используемого типа вставки данных:
# STH — это «дескриптор состояния» $STH = $DBH->prepare(«INSERT INTO folks ( first_name ) values ( ‘Cathy’ )»); $STH->execute();
Конечно, вы можете выполнить данную операцию с использованием метода exec(), при этом количество вызовов будет меньше на один. Но лучше использовать более длинный метод для получения преимуществ подготовленных выражений. Даже если Вы собираетесь использовть их один единственный раз, подготовленные выражения помогут Вам защититься от атак на Вашу систему.

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

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

Подготовленные выражения — это предварительно скомпилированные выражения SQL, которые могут быть выполнены много раз с помощью пересылки только данных на сервер. Они имеют дополнительное преимущество при автоматическом заполнении шаблона данными в виде защиты от атаки посредством вложений SQL кода.

Вы можете использовать подготовленные выражения с помощью включения шаблонов в ваш код SQL. Ниже приводятся 3 примера: один без шаблонов, один с неименованными шаблонами, один с именоваными шаблонами.
# нет шаблонов — открыто для атак путем внедрения SQL кода! $STH = $DBH->(«INSERT INTO folks (name, addr, city) values ($name, $addr, $city)»); # неименованые шаблоны $STH = $DBH->(«INSERT INTO folks (name, addr, city) values (?, ?, ?); # именованые шаблоны $STH = $DBH->(«INSERT INTO folks (name, addr, city) value (:name, :addr, :city)»);
Вам следует избегать использования первого метода. Выбор именованных или неименованных шаблонов влияет на то, как Вы устанавливаете данные для этих выражений.

Неименованные шаблоны

# назначение переменных каждому шаблону, индексируются от 1 до 3
$STH->bindParam(1, $name);
$STH->bindParam(2, $addr);
$STH->bindParam(3, $city);
# Вставляем одну строку $name = «Дима»
$addr = «ул. Лизюкова»;
$city = «Москва»;
$STH->execute();
# Вставляем другую строку
$name = «Сеня»
$addr = «Коммунистический тупик»;
$city = «Питер»;
$STH->execute();
Операция проходит в два этапа. На первом этапе шаблонам назначаются переменные. Затем, переменным присваиваются значения и выполняем выражение. Чтобы послать следующую порцию данных, нужно изменить значения переменных и выполнить выражение снова.

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

# Данные, которые надо вставить
$data = array(‘Моня’, ‘проспект Незабудок’, ‘Закутайск’);
$STH = $DBH->(«INSERT INTO folks (name, addr, city) values (?, ?, ?)»);
$STH->execute($data);

Данные в массиве подставляются в шаблоны в порядке следования. $data[0] идет в первый шаблон, $data[1] — во второй, и так далее. Однако, если массив проиндексирован в другом порядке, то такая операция будет выполняться некорректно. Вам нужно следить за соответствием порядка следования шаблонов и порядком расположения данных в массиве.

Если вы определили переменные знаком вопроса, то потом, в функцию execute передайте массив значений, в той, последовательности, в которой стоят переменные.

Если же вы обозначили переменные именами, то надо будет назначить каждой переменной значение посредством функций:

bindValue() – присваивает псевдопеременной значение

bindParam() – связывает псевдопеременную с настоящей переменной, и при изменении настоящей переменной, не нужно больше вызывать никаких дополнительных функций, можно сразу execute().

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

$sth3->bindParam(‘:id’,$id, PDO::PARAM_INT);

$sth3->bindParam(‘:id’,$id, PDO::PARAM_STR);

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

Именованные шаблоны

Вот пример использования именованного шаблона:

# Первый аргумент функции — имя именованного шаблона
# Именованный шаблон всегда начинается с двоеточия
$STH->bindParam(‘:name’, $name);
Вы можете использовать сокращения, но они работают с ассоциированными массивами. Пример:

# Данные, которые надо вставить
$data = array( ‘name’ => ‘Мишель’, ‘addr’ => ‘переулок Кузнечный’, ‘city’ => ‘Cnjkbwf’ );
# Сокращение
$STH = $DBH->(«INSERT INTO folks (name, addr, city) value (:name, :addr, :city)»);
$STH->execute($data);

Ключи Вашей таблицы не нуждаются в двоеточии, но тем не менее должны соответствовать именам шаблонов. Если Вы используете массив массивов, то можно проходить по нему и просто вызывать 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(‘Катя’,’проспект Ленина’,’Можайск’);
# Выполняем:
$STH = $DBH->(«INSERT INTO folks (name, addr, city) value (:name, :addr, :city)»);
$STH->execute((array)$cathy);

Преобразование типа объекта к array в execute приводит к обработке свойств как ключей массива.

Получение данных

Выборка данных
Для получения данных используется метод идентификатора состояния ->fetch(). Перед вызовом метода fetch() нужно указать PDO как Вы будете доставать данные из базы. Можно выбрать следующие опции:

  • PDO::FETCH_ASSOC: возвращает массив, индексированный по именам столбцов
  • PDO::FETCH_BOTH (default): возвращает массив, индексированный по именам столбцов и по номерам
  • 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. Пример выборки данных:

$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->address = preg_replace(‘/[a-z]/’, ‘x’, $this->address);
$this->other_data = $other;
} }

Как только данные извлечены в класс, все символы a-z в нижнем регистре в адресе будут заменены символом 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’ Вы увидите ’Лхххххххх хх-х 5’. Конечно, существуют ситуации, когда Вы хотите, чтобы конструктор был вызван перед тем, как будут назначены данные. 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’));

Если Вам нужно передать различные данные в конструктор для каждого объекта, Вы можете устанавливать режим извлечения данных внутри метода fetch:

$i = 0;
while($rowObj = $STH->fetch
(PDO::FETCH_CLASS, ‘secret_person’, array($i))) {
// do stuff $i++
}

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

Так как в короткой статье нельзя описать PDO полностью, то представим несколько полезных методов для выполнения базовых операций.
$DBH->lastInsertId();

Метод ->lastInsertId() всегда вызывается дескриптором базы данных (а не дескриптором состояния) и возвращает значение автоматически увеличивающегося идентификатора последней вставленной строки для данного соединения.

сли же в SQL выражении вы допустили ошибку, в PDOесть специальные функции:

errorCode() – возвращает номер ошибки, и

errorInfo() – возвращает массив, в котором, как номер ошибки, так и текст описания

Запросы непосредственно можно делать двумя функциями: exec() и query(). Отличие их состоит в типе возвращаемого результата, exec возвращает количество затронутых в результате выполнения запроса строк, а вторая, возвращает результат запроса в объекте PDOStatement.

$DBH->exec(‘DELETE FROM folks WHERE 1′); $DBH->exec(«SET time_zone = ‘-8:00′»);

Метод ->exec() используется для различных вспомогательных операций.

$safe = $DBH->quote($unsafe);

Метод ->quote() квотирует строки, так что они могут быть использованы в запросах. Это Ваш резерв на случай, если подготовленные выражения не используются.

$rows_affected = $STH->rowCount();

Метод ->rowCount() возвращает значение integer, указывающее количество строк, которые обрабатываются операцией. В последней версии PDO, в соответствии с отчетом об ошибках(http://bugs.php.net/40822) данный метод не работает с выражениями SELECT. Если у Вас возникли проблемы и Вы не можете обновить PHP, получить количество строк можно следующим способом:

$sql = «SELECT COUNT(*) FROM folks»;
if ($STH = $DBH->query($sql)) {
# Проверка количества строк
if ($STH->fetchColumn() > 0) {
# Здесь должен быть код SELECT
} else {
echo «Нет строк соответствующих запросу.»; } }

Транзакции

Транзакция – это совокупность запросов базу данных, которые должны быть обязательно выполнены все. Если какой-либо запрос не выполнен или выполнен с ошибкой, то транзакция отменяется и изменений данных в базе не происходит.

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

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

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

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

$db->beginTransaction();

После этого выполняем столько запросов к базе данных сколько необходимо сделать в этой транзакции.

И только после того как все запросы будут выполнены, Вы можете подтвердить транзакцию функцией

$db->commit();

или отменить транзакцию

$db->rollback();

Вот небольшой пример транзакций:

try
{
$connect_str = DB_DRIVER . ':host='. DB_HOST . ';dbname=' . DB_NAME;

$db = new PDO($connect_str,DB_USER,DB_PASS);

$rows = $db->exec("CREATE TABLE `testing`(
id INT PRIMARY KEY AUTO_INCREMENT,
fname VARCHAR(20) NOT NULL DEFAULT '',
email VARCHAR(50) NOT NULL DEFAULT '',
money INT NOT NULL DEFAULT 0) ENGINE=InnoDB;");

$rows = $db->exec("INSERT INTO `testing` VALUES
(null, 'Ivan', 'ivan@test.com', 15000),
(null, 'Petr', 'petr@test.com', 411000),
(null, 'Vasiliy', 'vasiliy@test.com', 1500000)
");

// Попробуем от Ивана перевести сумму 50000
// Петру

$summ = 50000;

$transaction = true;

$db->beginTransaction();

$sth1 = $db->query("SELECT money FROM testing WHERE fname='Ivan'");
$sth2 = $db->query("SELECT money FROM testing WHERE fname='Petr'");
$row1 = $sth1->fetch();
$row2 = $sth2->fetch();

if(!$row1 || !$row2) $transaction = false;
$count_money = $row1['money'];
$total2 = $summ + $row2['money'];
$total1 = $row1['money'] - $summ;

if($total1 < 0 || $total2 < 0) $transaction = false;

$num_rows1 = $db->exec("UPDATE `testing` SET money='" . $total1 . "' WHERE fname='Ivan'");
$num_rows2 = $db->exec("UPDATE `testing` SET money='" . $total2 . "' WHERE fname='Petr'");

if($transaction)
{
echo "Транзакция успешно прошла";
$db->commit();
}
else
{
echo "Транзакция не прошла";
$db->rollback();
}
}
catch(PDOException $e)
{
die("Error: ".$e->getMessage());
}

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

В заключении хотелось бы сказать, что это еще далеко не полный мануал по PDO. Всю самую свежую и полную информацию Вы всегда можете раздобыть вот здесь: http://www.php.net/manual/en/book.pdo.php

Комментарии запрещены.