На днях решил разобраться с php PDO. Это интересно и необходимо изучать новые технологии. Русской документации нигде не нашел, потому и решил выложить. Надеюсь эта статья будет Вам интересна.
Введение
Начнем сначала PHP Data Objects(PDO) – легкий интерфейс для доступа к базам данных в языке PHP. Он может работать с большинством баз данных, такими как MS SQL ,Firebird, MySQL , Oracle, PostgreSQL , SQLite и другими. Но тут необходимо обратить внимание, что PDO предоставляет необходимый функционал для работы с базами данных, но для каждого типа базы данных должен быть установлен свой драйвер доступа для базы данных в виде расширения PHP.
С помощью PDO можно создавать приложения полностью независимые от типа базы данных, при этом есть возможность использовать большую часть функционала баз данных, например создание подготовленных выражений или транзакции. В том случае, если данный функционал не поддерживается базой данных, PDO эмулирует эту функцию своими средствами, тем самым никак не нарушая логику программы.
Основы
Подключение довольно простое, за тем исключением, что теперь одной строкой необходимо сразу указать, к какому типу базы данных вы подключаетесь, имя хоста, а также имя базы данных.
Формат вот такой:
тип_базы_данных:host=имя_хоста;db=name
Давайте рассмотрим на примере, только немного усложним пример, использованием исключений из библиотеки PDO (PDOException). Чтобы в случае неудачного подключения к базе данных, мы получили вразумительное сообщение об этом, а не кучу косяков коде, неизвестно откуда взявшихся.
try
{
$db = new PDO('mysql:host=localhost;dbname=test','root','');
$rows = $db->exec("CREATE TABLE `testing`(
id INT PRIMARY KEY AUTO_INCREMENT,
fname VARCHAR(20) NOT NULL DEFAULT '',
email VARCHAR(50) NOT NULL DEFAULT '')");
}
catch(PDOException $e)
{
die("Error: ".$e->getMessage());
}
Если же в SQL выражении вы допустили ошибку, в PDO есть специальные функции:
errorCode() – возвращает номер ошибки, и
errorInfo() – возвращает массив, в котором, как номер ошибки, так и текст описания
Запросы непосредственно можно делать двумя функциями:
exec() и query()
Отличие их состоит в типе возвращаемого результата, exec возвращает количество затронутых в результате выполнения запроса строк, а вторая, возвращает результат запроса в объекте PDOStatement, о нем поговорим чуть ниже.
Теперь добавим эти функции в код и сделаем пример чуть более сложным:
// в начале конфиг
define('DB_DRIVER','mysql');
define('DB_HOST','localhost');
define('DB_NAME','test');
define('DB_USER','root');
define('DB_PASS','');
try
{
// соединяемся с базой данных
$connect_str = DB_DRIVER . ':host='. DB_HOST . ';dbname=' . DB_NAME;
$db = new PDO($connect_str,DB_USER,DB_PASS);
// вставляем несколько строк в таблицу из прошлого примера
$rows = $db->exec("INSERT INTO `testing` VALUES
(null, 'Ivan', 'ivan@test.com'),
(null, 'Petr', 'petr@test.com'),
(null, 'Vasiliy', 'vasiliy@test.com')
");
// в случае ошибки SQL выражения выведем сообщене об ошибке
$error_array = $db->errorInfo();
if($db->errorCode() != 0000)
echo "SQL ошибка: " . $error_array[2] . '<br />';
// если запрос был выполнен успешно,
// то выведем количество затронутых строк
if($rows) echo "Количество затронутых строк: " . $rows. "<br />";
// теперь выберем несколько строчек из базы
$result = $db->query("SELECT * FROM `testing` LIMIT 2");
// в случае ошибки SQL выражения выведем сообщене об ошибке
$error_array = $db->errorInfo();
if($db->errorCode() != 0000)
echo "SQL ошибка: " . $error_array[2] . '<br /><br />';
// теперь получаем данные из класса PDOStatement
while($row = $result->fetch())
{
// в результате получаем ассоциативный массив
print_r($row);
}
}
catch(PDOException $e)
{
die("Error: ".$e->getMessage());
}
Подготовленные выражения
Подготовленные выражения очень похожи на обычные SQL запросы, но имеют некоторые преимущества. Во-первых имеют большую скорость выполнения, а во-вторых являются более надежными с точки зрения безопасности, так как все параметры передаваемые в них, автоматически экранируются от всевозможных инъекций.
Они имеют весомое преимущество в скорости, при выполнении многократных одинаковых запросов, чем если каждый раз составлять запрос заново. Также экономится траффик между приложением и базой данных.
PDO предоставляет удобные функции для работы с подготовленными выражениями. В случае, если выбранный тип базы данных не поддерживает работу с подготовленными выражениями, PDO просто будет эмулировать их работу своими методами.
И так для начала создадим подготовленное выражение, это делается функцией Prepare()
В качестве параметра она принимает SQL запрос, но в нем, вместо значений, которые необходимо менять, ставятся псевдо переменные, которые могут быть в виде знака вопроса(?), либо имени псевдо переменой, которое начинается с двоеточия (:)
$sth1 = $db->prepare(“SELECT * FROM `testing` WHERE id=:id”);
$sth2 = $db->prepare(“SELECT * FROM `testing` WHERE id=?”);
В зависимости от того, как вы определите переменную, будет зависеть ваша дальнейшая работа.
Если вы определили переменные знаком вопроса, то потом, в функцию execute передайте массив значений, в той, последовательности, в которой стоят переменные.
Если же вы обозначили переменные именами, то надо будет назначить каждой переменной значение посредством функций:
bindValue() – присваивает псевдопеременной значение
bindParam() – связывает псевдопеременную с настоящей переменной, и при изменении настоящей переменной, не нужно больше вызывать никаких дополнительных функций, можно сразу execute()
Вот пример использования первого варианта:
А теперь второй способ.
Для присваивания значения псевдо переменной, воспользуемся функцией bindValue()
$sth3 = $db->prepare("SELECT * FROM `testing` WHERE id=:id");
for($id=1; $id < 4; $id++)
{
$sth3->bindValue(':id',$id);
$sth3->execute();
while($row = $sth3->fetch())
{
print_r($row);
}
}
Этот код можно записать еще проще, связав псевдопеременную с реальной:
$sth3 = $db->prepare("SELECT * FROM `testing` WHERE id=:id");
$sth3->bindParam(':id',$id);
for( $id = 1; $id < 4; $id++)
{
$sth3->execute();
while($row = $sth3->fetch())
print_r($row);
}
Тут же необходимо добавить, что очень желательно (чтобы не возникало лишних ошибок) третьим параметром указывать тип переменной. У меня лично, в случае отсутствия типа переменной, возникали ошибки в операторе WHERE, так как он считал переменную текстом, а не числом.
$sth3->bindParam(‘:id’,$id, PDO::PARAM_INT);
$sth3->bindParam(‘:id’,$id, PDO::PARAM_STR);
Еще одним из очень приятных плюсов использования таких подготовленных выражений, это экранирование переменных. Перед подстановкой в процедуру все переменные экранируются и никакие SQL инъекции не страшны.
Транзакции
Транзакция – это совокупность запросов базу данных, которые должны быть обязательно выполнены все. Если какой-либо запрос не выполнен или выполнен с ошибкой, то транзакция отменяется и изменений данных в базе не происходит.
Это нужно, чтобы гарантировать сохранение целостности данных при нескольких запросах. например при переводе денежных средств со счета на счет.
Чтобы выполнить транзакцию в 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;
$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
Изучайте и создавайте.
Комментарии
15 Sep, 2011в10:43
За помощь в написании статьи и обнаружении ошибок в ней, хотелось бы Выразить благодарность Михаилу. Ссылку на свой сайт он, к сожалению, не оставил.
24 Oct, 2011в10:27
Спасибо за статтю! Помогла разобраться с основами PHP PDO.
28 Oct, 2011в18:25
А SQLSTATE error code он из себя, что представляет, строку? И когда вы проверяете (2-ой исходник)
if($db->errorCode() != 0000)
тут не пять нолей?
29 Oct, 2011в07:12
Нет, SQLSTATE error code это число, потому можно сравнивать хоть с 0 хоть с 0000. Просто PDO по-моему возвращаед код четырехразрядным, потому и сравниваю с 0000.
07 Feb, 2012в21:41
Не вводите народ в заблуждение.
>Для тех, кто еще не знаком с подготовленными выражениями,
>это аналог процедур в языке программирования, они это
>набор SQL инструкций, которые компилируются и хранятся
>на сервере, они имеют входные параметры, и могут
>вызываться многократно с новыми значениями.
Вы попутали подготовленные выражения с хранимыми процедурами. В тексте вы описываете именно хранимые процедуры.
08 Feb, 2012в05:59
Добрый день, да, мой косяк, я забыл убрать этот абзац.
Ну везде я описываю, как работать именно с подготовленными выражениями.
Хранимые процедуры, я думаю опишу в следующей статье.
А обзац, поправил.
Благодарю.
08 Mar, 2012в17:26
Дмитрий, спасибо за статью!
Прошу помочь. В моем приложении после применения INSERT необходимо получить значение автоинкрементного поля id добавленной записи (при процедурном подходе используется функция mysql_insert_id()). Как получить данное значение при использовании PDO?
12 Apr, 2012в04:15
Спасибо.
Короткое и доходчивое описание с примерами.
12 Apr, 2012в09:21
Добрый день, Виталий. тут тоже все довольно просто, у самого объекта PDO есть удобная функция “lastInsertId”, только тут есть одна особенность. Если вы создали подготовленное выражение
$sth = $db->prepare();
То, функцию эту надо вызывать из самого объекта PDO:
$last_insert_id = $db->lastInsertId();
28 Jul, 2012в16:07
Спасибо! Объяснили =)
10 Sep, 2012в17:37
У вас опечатка в одном из исходных кодов. “SELECT1″ вместо “SELECT”
20 Sep, 2012в09:33
Поправил, спасибо.
25 Sep, 2012в11:10
а преременная $count_money там не лишняя ли?
25 Sep, 2012в12:02
Ох как давно я это писал, наверное лишнее что-то осталось. Ну я убрал, благодарю, что вдумываетесь в то что я тут пишу.
07 Nov, 2012в08:42
Спасибо огромное!
Помогли разобраться!
18 Feb, 2013в14:22
Спасибо за доходчивый расклад. Хотел бы добавить, что уже доступна и русская версия документации php и PDO:
http://www.php.net/manual/ru/book.pdo.php