Категория pdo. Почему стоит пользоваться PDO для работы с базой данных

На днях решил разобраться с 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", "[email protected]"), (null, "Petr", "[email protected]"), (null, "Vasiliy", "[email protected]") " ) ; $error_array = $db -> errorInfo () ; if ($db ->
" ; // если запрос был выполнен успешно, // то выведем количество затронутых строк if ($rows ) echo "Количество затронутых строк: " . $rows . "
" ; // теперь выберем несколько строчек из базы $result = $db -> query ("SELECT * FROM `testing` LIMIT 2" ) ; // в случае ошибки SQL выражения выведем сообщене об ошибке $error_array = $db -> errorInfo () ; if ($db -> errorCode () != 0000) echo "SQL ошибка: " . $error_array [ 2 ] . "

" ; // теперь получаем данные из класса 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()

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


Обратите внимание, что после создания объектом PDO подготовленного выражения в классе PDOStatement, мы пользуемся только им, соответственно, в нем есть свои функции errorCode, errorInfo, а также результат выполнения запросов, также сразу же хранится в нем.

А теперь второй способ.

Для присваивания значения псевдо переменной, воспользуемся функцией bindValue()

Этот код можно записать еще проще, связав псевдопеременную с реальной:

Тут же необходимо добавить, что очень желательно (чтобы не возникало лишних ошибок) третьим параметром указывать тип переменной. У меня лично, в случае отсутствия типа переменной, возникали ошибки в операторе 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", "[email protected]", 15000), (null, "Petr", "[email protected]", 411000), (null, "Vasiliy", "[email protected]", 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 ( . $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

Изучайте и создавайте.

Сегодня я начинаю цикл статей, посвящённых 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 .

Я думаю настало время повысить свой экспириенс, и перейти при работе с базой данных с mysql_ функций на PDO. Эта библиотека является мощным и быстрым дополнением к PHP. Одно из ее преимуществ – работа со многими базами данных (MS SQL , MySQL , PostgreSQL , Oracle и др.). Также отличительной характеристикой этой библиотеки являются подготовленные выражения, так называемые prepared statements, которые должны ускорить работу с базой, а главное сделать обмен данным безопасными и забыть о таких уязвимостях как sql-enjection. Кроме того, есть и другие очень полезные возможности. В этой статье я попытался собрать часто используемые примеры работ, по которым можно сразу понять суть работы PDO.

В PHP существуют три расширения для работы с базой MySQL: mysql, mysqli и PDO. PHP PDO (PHP Data Objects) включено в PHP 5.1 и выше. Как я понял, на сегодняшний момент, функции для работы с базой данных mysql_ не рекомендуют к использованию, так как разработка php_mysql остановилась на поддержке функционала MySQL 4.1.3. и также не поддерживает транзакции, объектный интерфейс и подвержено уязвимостям при подстановке значений в запрос. После mysql_ появилось расширение mysqli (MySQL Improved в 5 версии), которое поддерживает новые возможности MySQL и использует как ОПП синтаксис так и процедурное программирование. Все эти библиотеки используют стандартную клиентскую библиотеку MySQL (libmysql). В этой же заметке давайте на живых примерах посмотрим как осуществляется работа с mysql, при помощи самого свежего расширения – PDO .

PDO Соединение с БД //пример соединения с MySQL при помощи PDO $db = new PDO("mysql:host=localhost;dbname=test", $user, $pass);

Как только будет установлено успешное соединение с конкретным сервером базы данных, то будет возвращен объект PDO. Этот объект позволяет выполнять самые разнообразные задачи баз данных.

Если есть какие-либо ошибки подключения, то сработает механизм исключений – PDOException. Нужно всегда оборачивать операции PDO в блок try/catch. Вы можете поймать исключение, если вы хотите обрабатывать ошибки, или вы можете оставить его для глобального обработчика исключений (exception), которые вы создали с помощью set_exception_handler (). В PDO есть специальные функции для ошибок: errorCode() – вернет номер ошибки, errorInfo() – вернет массив с номером ошибки и описанием. Они нужны так как по умолчанию режим работы с ошибками стоит ERRMODE_SILENT. В этом случае чтобы увидеть эти ошибки придется их вызвать:

Echo $conn->errorCode(); echo $conn->errorInfo();

Чтобы этого не делать, в режиме разработке проще сразу выставить нужный режим работы с ошибками: ATTR_ERRMODE и ERRMODE_EXCEPTION. Можно также прописать кодировку работы с базой. В итоге у нас получится такой вот код подключения:

Try { $db = new PDO("mysql:host=$host;dbname=$dbname", $user, $password); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->exec("set names utf8"); } catch(PDOException $e) { echo $e->getMessage(); }

После успешного подключения к базе данных, экземпляр класса PDO возвращается в сценарий. $db содержит дескриптор базы данных. Соединение остается активным в течение всей жизни объекта PDO. Чтобы закрыть соединение, вам нужно уничтожить объект с гарантией, что все остальные ссылки на него будут удалены. Сделать это можно путем присвоения переменной, которая содержит объект, значения NULL. Если вы не сделаете этого явно, PHP будет автоматически закрывать соединение после завершения работы скрипта.

//Закрытие соединения $db = null;

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

//Постоянное соединение $dbh = new PDO("mysql:host=localhost;dbname=test", $user, $pass, array(PDO::ATTR_PERSISTENT => true));

Теперь, когда вы увидели, как открывать и закрывать соединение, давайте рассмотрим другие примеры работы с PDO. В данном случае я собираюсь показать вам, как выполнять запросы к конкретной базе данных. Запросы можно делать 3 функциями: exec(), query() и prepare+execute.

Exec()

Первый – exec вернет только кол-во задействованных строк или FALSE при ошибке и используется там, где не возвращаются данные, например при удалении:

//использование метода exec() try{ $db = new PDO("mysql:host=localhost;dbname=test","user","password"); $delrows=$db->exec("DELETE FROM users WHERE id>20"); echo "Количество удаленных строк: ".$delrows; } catch(PDOException $e){ echo "Error: ".$e->getMessage(); exit(); } //Еще $db->exec("DELETE FROM folks WHERE 1"); //или $db->exec("SET time_zone = "-8:00""); //или $db->exec("CREATE TABLE `test`(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL DEFAULT "", email VARCHAR(50) NOT NULL DEFAULT "")"); //или $db->exec("SET CHARACTER SET utf8");

Query()

Второй – query() вернет результат в объекте PDOStatement. Также возвращает результат или FALSE при ошибке. Ему можно доверить простые запросы. Можно использовать query() с условием (я правда не знаю зачем кому то это может понадобиться), но тогда все равно придется экранировать данные методом PDO::quote

//Простые запросы $db->query("SET CHARACTER SET utf8"); $db->query("SELECT * FROM users"); //Можно вычислить количество строк $stmt = $db->query("SELECT * FROM table"); $row_count = $stmt->rowCount(); echo $row_count." rows selected"; //Еще вариант с количеством $stmt = $db->query("SELECT * from users"); $rows = $stmt->fetchAll(); $count = count($rows); foreach($rows as $row) { print_r($row); } //Запрос с условием и экранированием $conn->query("SELECT * FROM table WHERE id = " . $conn->quote($id));

lastInsertId() возвращает идентификатор последней вставленной строки базы данных.

//метод lastInsertId() возвращает id последней записи $db->query("INSERT INTO users SET name="Vasya",address="Here",email="[email protected]""); $insertId=$db->lastInsertId();

Подготовленные выражения – prepared statments.

Третий способ - prepare+execute - Подготовленные выражения , они же подготовленные инструкции они же плейсхолдеры они же prepared statments или связываемые переменные, позволяют определить выражение один раз, а затем многократно его выполнять с разными параметрами. Также они позволяют отделить переменные от запроса, что делает код безопаснее и повышает скорость выполнения. В вашем коде больше не надо будет пытаться очистить передаваемые данные. Мы сделаем это только один раз перед выполнением запроса к БД. Для этого используем функцию Prepare() ; В качестве параметра она принимает SQL запрос, но в нем, вместо переменных используются метки, в виде знака вопроса ‘?’ или номеров ‘:1′, или переменой, имя которой начинается с двоеточия ‘:’. Если вы остановились на знаках вопроса (:цифрах), то вам надо в функцию execute передать массив значений, в соответствующей последовательности. Если ваш выбор именованные переменные, то надо назначить каждой переменной значение через одну из двух функций: либо bindValue(), которая присваивает псевдо-переменной значение, либо bindParam(), которая связывает псевдо-переменную с настоящей переменной. Третьим параметром можно указать тип переменной, например $db->bindParam(‘:id’,$id, PDO::PARAM_INT).

//Не именованные метки try { $stmt = $db->prepare("INSERT INTO test (label,color) VALUES (?,?)"); $stmt -> execute(array("perfect","green")); } catch(PDOException $e){ echo "Error: ".$e->getMessage(); exit(); } //stmt - это "дескриптор состояния" //Именованные метки $stmt = $db->prepare("INSERT INTO test (label,color) VALUES (:label,:color)"); $stmt -> execute(array("label"=>"perfect", "color"=>"green")); //Еще вариант $stmt = $db->prepare("INSERT INTO users (firstname, lastname, email) VALUES (:firstname, :lastname, :email)"); $stmt->bindParam(":firstname", $firstname); $stmt->bindParam(":lastname", $lastname); $stmt->bindParam(":email", $email); $firstname = "John"; $lastname = "Smith"; $email = "[email protected]"; $stmt->execute();

Еще раз напомню, что если вы не используете подготовленные выражения но все-таки хотите обезопасить передаваемые данные, то это можно сделать при помощи функции PDO:quote.

PDO SELECT данных

Для выборки данных используются методы fetch() или fetchAll(). Перед вызовом функции нужно указать PDO как Вы будете доставать данные из базы. PDO::FETCH_ASSOC вернет строки в виде ассоциативного массива с именами полей в качестве ключей. PDO::FETCH_NUM вернет строки в виде числового массива. По умолчанию выборка происходит с PDO::FETCH_BOTH, который дублирует данные как с численными так и с ассоциативными ключами, поэтому рекомендуется указать один способ, чтобы не иметь дублирующих массивов:

$stmt = $db->query("SELECT * from users"); //Установка fetch mode $stmt->setFetchMode(PDO::FETCH_ASSOC); while($row = $stmt->fetch()) { echo "

" . $row["firstname"] . " " . $row["lastname"] . "

"; echo "

" . $row["email"] . "


"; }

Либо можно указать метод выборки в самом методе ->fetch()

$stmt = $db->query("SELECT * FROM table"); while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row["field1"]." ".$row["field2"]; //и т.д... } //Внимание, не работающий пример с LIKE! $stmt = $db->prepare("SELECT field FROM table WHERE field LIKE %?%"); $stmt->bindParam(1, $search, PDO::PARAM_STR); $stmt->execute(); //Нужно так: $stmt = $db->prepare("SELECT field FROM table WHERE field LIKE ?"); $stmt->bindValue(1, "%$search%", PDO::PARAM_STR); $stmt->execute(); //Еще пример $stmt = $db->prepare("SELECT * FROM table WHERE id=? AND name=?"); $stmt->bindValue(1, $id, PDO::PARAM_INT); $stmt->bindValue(2, $name, PDO::PARAM_STR); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); //Еще вариант $stmt = $db->prepare("SELECT * from users"); $stmt -> execute(); while($row = $stmt->fetch()) { print_r($row); }

PDO UPDATE данных

Происходит по существу также как и INSERT и SELECT (в данном случае опять же будем использовать именованные метки (placeholders)):

$stmt = $db->prepare("UPDATE users set email = :email where lastname=:lastname"); $stmt->bindParam(":lastname", $lastname); $stmt->bindParam(":email", $email); $lastname = "Smith"; $email = "[email protected]"; $stmt->execute();

DELETE Удаление происходит простейшим образом:

$db->exec("DELETE FROM users");

Конечно вы можете также использовать именованные параметры (placeholders) при удалении.

Если у вас есть свои часто используемые заготовки при работе с расширением PHP PDO, буду признателен если вы ими поделитесь. Ссылка на мануал

Если у вас возникла ошибка Undefined variable: DBH … то можете почитать как это исправить.

Google помог найти шпаргалку по PDO, может кому пригодится:

Для начала создадим базу данных для этого руководства:

CREATE DATABASE solar_system; GRANT ALL PRIVILEGES ON solar_system.* TO "testuser"@"localhost" IDENTIFIED BY "testpassword";

Пользователю с логином testuser и паролем testpassword предоставили полные права доступа к базе solar_system .

Теперь создадим таблицу и заполним данными, астрономическая точность которых не подразумевается:

USE solar_system; CREATE TABLE planets (id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id), name VARCHAR(10) NOT NULL, color VARCHAR(10) NOT NULL); INSERT INTO planets(name, color) VALUES("earth", "blue"), ("mars", "red"), ("jupiter", "strange");

Описание соединения

Теперь, когда создана база, определим DSN () - сведения для подключения к базе, представленные в виде строки. Синтаксис описания отличается в зависимости от используемой СУБД. В примере работаем с MySQL/MariaDB, поэтому указываем:

  • имя хоста, где расположена СУБД;
  • порт (необязательно, если используется стандартный порт 3306);
  • имя базы данных;
  • кодировку (необязательно).

Строка DSN в этом случае выглядит следующим образом:

$dsn = "mysql:host=localhost;port=3306;dbname=solar_system;charset=utf8";

Первым указывается database prefix . В примере - mysql . Префикс отделяется от остальной части строки двоеточием, а каждый следующий параметр - точкой с запятой.

Создание PDO-объекта

Теперь, когда строка DSN готова, создадим PDO-объект. Конструктор на входе принимает следующие параметры:

  • Строку DSN.
  • Имя пользователя, имеющего доступ к базе данных.
  • Пароль этого пользователя.
  • Массив с дополнительными параметрами (необязательно).
  • $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]; $pdo = new PDO($dsn, "testuser", "testpassword", $options);

    Дополнительные параметры можно также определить после создания объекта с помощью метода SetAttribute:

    $pdo->SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    Определение метода выборки по умолчанию

    PDO::DEFAULT_FETCH_MODE - важный параметр, который определяет метод выборки по умолчанию. Указанный метод используется при получении результата выполнения запроса.

    PDO::FETCH_BOTH

    Режим по умолчанию. Результат выборки индексируется как номерами (начиная с 0), так и именами столбцов:

    $stmt = $pdo->query("SELECT * FROM planets"); $results = $stmt->fetch(PDO::FETCH_BOTH);

    После выполнения запроса с этим режимом к тестовой таблице планет получим следующий результат:

    Array ( => 1 => 1 => earth => earth => blue => blue)

    PDO::FETCH_ASSOC

    Результат сохраняется в ассоциативном массиве, в котором ключ - имя столбца, а значение - соответствующее значение строки:

    $stmt = $pdo->query("SELECT * FROM planets"); $results = $stmt->fetch(PDO::FETCH_ASSOC);

    В результате получим:

    Array ( => 1 => earth => blue)

    PDO::FETCH_NUM

    При использовании этого режима результат представляется в виде массива, индексированного номерами столбцов (начиная с 0):

    Array ( => 1 => earth => blue)

    PDO::FETCH_COLUMN

    Этот вариант полезен, если нужно получить перечень значений одного поля в виде одномерного массива, нумерация которого начинается с 0. Например:

    $stmt = $pdo->query("SELECT name FROM planets");

    В результате получим:

    Array ( => earth => mars => jupiter)

    PDO::FETCH_KEY_PAIR

    Используем этот вариант, если нужно получить перечень значений двух полей в виде ассоциативного массива. Ключи массива - это данные первого столбца выборки, значения массива - данные второго столбца. Например:

    $stmt = $pdo->query("SELECT name, color FROM planets"); $result = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);

    В результате получим:

    Array ( => blue => red => strange)

    PDO::FETCH_OBJECT

    При использовании PDO::FETCH_OBJECT для каждой извлеченной строки создаётся анонимный объект. Его общедоступные (public) свойства - имена столбцов выборки, а результаты запроса используются в качестве их значений:

    $stmt = $pdo->query("SELECT name, color FROM planets"); $results = $stmt->fetch(PDO::FETCH_OBJ);

    В результате получим:

    StdClass Object ( => earth => blue)

    PDO::FETCH_CLASS

    В этом случае, как и в предыдущем, значения столбцов становятся свойствами объекта. Однако требуется указать существующий класс, который будет использоваться для создания объекта. Рассмотрим это на примере. Для начала создадим класс:

    Class Planet { private $name; private $color; public function setName($planet_name) { $this->name = $planet_name; } public function setColor($planet_color) { $this->color = $planet_color; } public function getName() { return $this->name; } public function getColor() { return $this->color; } }

    Обратите внимание, что у класса Planet закрытые (private) свойства и нет конструктора. Теперь выполним запрос.

    Если используется метод fetch с PDO::FETCH_CLASS , перед отправкой запроса на получение данных нужно применить метод setFetchMode:

    $stmt = $pdo->query("SELECT name, color FROM planets"); $stmt->setFetchMode(PDO::FETCH_CLASS, "Planet");

    Первый параметр, который передаем методу setFetchMode , - константа PDO::FETCH_CLASS . Второй параметр - имя класса, который будет использоваться при создании объекта. Теперь выполним:

    $planet = $stmt->fetch(); var_dump($planet);

    В результате получим объект Planet:

    Planet Object ( => earth => blue)

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

    Определение свойств после выполнения конструктора

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

    При использовании константы FETCH_PROPS_LATE значения свойств будут присваиваться после выполнения конструктора:

    Class Planet { private $name; private $color; public function __construct($name = moon, $color = grey) { $this->name = $name; $this->color = $color; } public function setName($planet_name) { $this->name = $planet_name; } public function setColor($planet_color) { $this->color = $planet_color; } public function getName() { return $this->name; } public function getColor() { return $this->color; } }

    Мы изменили класс Planet , добавив конструктор, который принимает на входе два аргумента: name (имя) и color (цвет). Значения этих полей по умолчанию: moon (луна) и gray (серый) соответственно.

    Если не использовать FETCH_PROPS_LATE , при создании объекта свойства будут перезаписаны значениями по умолчанию. Проверим это. Сначала выполним запрос:

    $stmt = $pdo->query("SELECT name, color FROM solar_system WHERE name = "earth""); $stmt->setFetchMode(PDO::FETCH_CLASS, "Planet"); $planet = $stmt->fetch(); var_dump($planet);

    В результате получим:

    Object(Planet)#2 (2) { ["name":"Planet":private]=> string(4) "moon" ["color":"Planet":private]=> string(4) "gray" }

    Как и ожидалось, извлеченные из базы данных значения перезаписаны. Теперь рассмотрим решение задачи с помощью FETCH_PROPS_LATE (запрос аналогичный):

    $stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Planet"); $planet = $stmt->fetch(); var_dump($planet);

    В результате получим то, что нужно:

    Object(Planet)#4 (2) { ["name":"Planet":private]=> string(5) "earth" ["color":"Planet":private]=> string(4) "blue" }

    Если у конструктора класса нет значений по умолчанию, а они нужны, параметры конструктора задаются при вызове метода setFetchMode третьим аргументом в виде массива. Например:

    Class Planet { private $name; private $color; public function __construct($name, $color) { $this->name = $name; $this->color = $color; } [...] }

    Аргументы конструктора обязательны, поэтому выполним:

    $stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Planet", ["moon", "gray"]);

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

    Получение нескольких объектов

    Множественные результаты извлекаются в виде объектов с помощью метода fetch внутри цикла while:

    While ($planet = $stmt->fetch()) { // обработка результатов }

    Или путём выборки всех результатов сразу. Во втором случае используется метод fetchAll , причём режим указывается в момент вызова:

    $stmt->fetchAll(PDO::FETCH_CLASS|PDO_FETCH_PROPS_LATE, "Planet", ["moon", "gray"]);

    PDO::FETCH_INTO

    При выборе этого варианта выборки PDO не создаёт новый объект, а обновляет свойства существующего. Однако это возможно только для общедоступных (public) свойств или при использовании в объекте «магического» метода __set .

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

    В PDO два способа выполнения запросов:

    • прямой, который состоит из одного шага;
    • подготовленный, который состоит из двух шагов.
    Прямые запросы

    Существует два метода выполнения прямых запросов:

    • query используется для операторов, которые не вносят изменения, например SELECT . Возвращает объект PDOStatemnt , из которого с помощью методов fetch или fetchAll извлекаются результаты запроса;
    • exec используется для операторов вроде INSERT , DELETE или UPDATE . Возвращает число обработанных запросом строк.

    Прямые операторы используются только в том случае, если в запросе отсутствуют переменные и есть уверенность, что запрос безопасен и правильно экранирован.

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

    PDO поддерживает подготовленные запросы (prepared statements), которые полезны для защиты приложения от : метод prepare выполняет необходимые экранирования.

    Рассмотрим пример. Требуется вставить свойства объекта Planet в таблицу Planets . Сначала подготовим запрос:

    $stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(?, ?)");

    Используем метод prepare , который принимает как аргумент SQL-запрос с псевдопеременными (placeholders). Псевдопеременные могут быть двух типов: неименнованые и именованные.

    Неименованные псевдопеременные

    Неименованные псевдопеременные (positional placeholders) отмечаются символом? . Запрос в результате получается компактным, но требуется предоставить значения для подстановки, размещенные в том же порядке. Они передаются в виде массива через метод execute:

    $stmt->execute([$planet->name, $planet->color]);

    Именованные псевдопеременные

    При использовании именованных псевдопеременных (named placeholders) порядок передачи значений для подстановки не важен, но код в этом случае становится не таким компактным. В метод execute данные передаются в виде ассоциативного массива, в котором каждый ключ соответствует имени псевдопеременной, а значение массива - значению, которое требуется подставить в запрос. Переделаем предыдущий пример:

    $stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)"); $stmt->execute(["name" => $planet->name, "color" => $planet->color]);

    Методы prepare и execute используются как при выполнении запросов на изменение, так и при выборке.

    А информацию о количестве обработанных строк при необходимости предоставит метод rowCount .

    Управление поведением PDO при ошибках

    Параметр выбора режима ошибок PDO::ATTR_ERRMODE используется для определения поведения PDO в случае ошибок. Доступно три варианта: PDO::ERRMODE_SILENT , PDO::ERRMODE_EXCEPTION и PDO::ERRMODE_WARNING .

    PDO::ERRMODE_SILENT

    Вариант по умолчанию. PDO просто запишет информацию об ошибке, которую помогут получить методы errorCode и errorInfo .

    PDO::ERRMODE_EXCEPTION

    Это предпочтительный вариант, при котором в дополнение к информации об ошибке PDO выбрасывает исключение (PDOException). Исключение прерывает выполнение скрипта, что полезно при использовании транзакций PDO. Пример приведён при описании транзакций.

    PDO::ERRMODE_WARNING

    В этом случае PDO также записывает информацию об ошибке. Поток выполнения скрипта не прерывается, но выдаются предупреждения.

    Методы bindValue и bindParam

    Для подстановки значений в запросе можно также использовать методы bindValue и bindParam . Первый связывает значение переменной с псевдопеременной, которая использована при подготовке запроса:

    $stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)"); $stmt->bindValue("name", $planet->name, PDO::PARAM_STR);

    Связали значение переменной $planet->name с псевдопеременной:name . Обратите внимание, что при использовании методов bindValue и bindParam как третий аргумент указывается тип переменной, используя соответствующие константы PDO. В примере - PDO::PARAM_STR .

    Метод bindParam привязывает переменную к псевдопеременной. В этом случае переменная связана с псевдопеременной ссылкой, а значение будет подставлено в запрос только после вызова метода execute . Рассмотрим на примере:

    $stmt->bindParam("name", $planet->name, PDO::PARAM_STR);

    Транзакции в PDO

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

    $pdo->beginTransaction(); try { $stmt1 = $pdo->exec("DELETE FROM planets"); $stmt2 = $pdo->prepare("INSERT INTO planets(name, color) VALUES (?, ?)"); foreach ($planets as $planet) { $stmt2->execute([$planet->getName(), $planet->getColor()]); } $pdo->commit(); } catch (PDOException $e) { $pdo->rollBack(); }

    Метод beginTransaction отключает автоматическое выполнение запросов, а внутри конструкции try-catch запросы выполняются в нужном порядке. Если не возникнет исключений PDOException , запросы выполнятся с помощью метода commit . В противном случае откатятся с помощью метода rollback , а автоматическое выполнение запросов восстановится.

    Таким образом появилась согласованность выполнения запросов. Очевидно, что для этого параметру PDO::ATTR_ERRMODE необходимо установить значение PDO::ERRMODE_EXCEPTION .

    Заключение

    Теперь, когда работа с PDO описана, отметим его основные преимущества:

    • с PDO легко перенести приложение на другие СУБД;
    • поддерживаются все популярные СУБД;
    • встроенная система управления ошибками;
    • разнообразные варианты представления результатов выборки;
    • поддерживаются подготовленные запросы, которые сокращают код и делают его устойчивым к SQL-инъекциям;
    • поддерживаются транзакции, которые помогают сохранить целостность данных и согласованность запросов при параллельной работе пользователей.

    Александр Наливайко , переводчик

    Как Яндекс использует ваши данные и машинное обучение для персонализации сервисов - .

    Фреймворк 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 с нуля с использованием ООП:

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