VDS за 300р от нашего партнера SpaceWeb
[315]
29 Июл 2012, 22:23

PHP работа с PDO

Привет %username%!

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

В чем преимущество PDO?

Этот вопрос можно раскрыть тремя пунктами.

  1. Универсальный интерфейс для работы с различными базами данных. Разработчик может легко перевести свое веб-приложение на другу СУБД, поменяв при этом всего пару строк кода;
  2. Высокая скорость работы;
  3. Подготовленные выражения, о которых мы поговорим чуть позже.

На данный момент расширение 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 )

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

В зависимости от выбранной СУБД, способ подключения может незначительно отличаться. Подключение к популярным СУБД:

//MуSQL $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
//PostgreSQL $DBH = new PDO("pgsql:host=$host;dbname=$dbname", $user, $pass);
//MS SQL $DBH = new PDO("mssql:host=$host;dbname=$dbname", $user, $pass);
//SQLite $DBH = new PDO("sqlite:my/database/path/database.db");

Обработка ошибок и исключения

Обязательно заключайте подключение к базе данных в блок try/catch.

try { $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass); } catch(PDOException $e) { echo "Нет соединения с базой данных"; }

Иначе в случае ошибки, в браузер будет выкинут Fatal error, в котором будут раскрыты все подробности вашего соединения, с логином и паролем.

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

PDO имеет три режима обработки исключения:

PDO::ERRMODE_SILENT — Это режим по умолчанию. Ошибки генерируются по такому же принципу, как в расширениях mysql или mysqli. После возникновения ошибки скрипт продолжит работу.

PDO::ERRMODE_WARNING — режим вызовет стандартное сообщение E_WARNING и позволит скрипту продолжить работу.

PDO::ERRMODE_EXCEPTION — режим выбрасывает исключение, что позволяет обрабатывать ошибки и скрывать важную информацию от посторонних глаз.

Что бы установить необходимый уровень контроля ошибок необходимо вызвать метод $this->setAttribute после подключения к базе данных.

try { $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $e) { echo "Нет соединения с базой данных"; }

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

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

За подготовленные выражения отвечает метод $DBH->prepare. Им и рекомендуется всегда пользоваться.

Сразу хочу отметить, что выполнить запрос в PDO можно тремя методами.

$DBH->exec — используется для запросов, которые не возвращают никаких данных. Метод возвращает количество затронутых им записей, или FALSE в случае ошибки.

$count_row = $DBH->exec("DELETE FROM users");

$DBH->query – используется выполнения не защищенных запросов, и возвращает результат или FALSE в случаи ошибки. Например, им можно выполнять простые запросы.

$DBH->query("SET NAMES 'cp1251'"); $DBH->query("SELECT * FROM users"); $DBH->query("DELETE FROM users");

$DBH->prepare + $STH->execute — используется для подготовки выражений и выполнения запроса.

// безымянные placeholders $STH = $DBH->prepare("INSERT INTO users (name, phone, city) values (?, ?, ?)");
// именные placeholders $STH = $DBH->prepare("INSERT INTO users (name, phone, city) values (:name, :phone, :city)");

После подготовки, запрос выполняться методом $STH->execute($data).

// безымянные placeholders
$data = array(); $data[] = 'Alersander'; $data[] = '+7 000 123 45 67'; $data[] = 'St. Petersburg';
$STH = $DBH->prepare("INSERT INTO users (name, phone, city) values (?, ?, ?)"); $STH->execute($data);
// именные placeholders
$data = array(); $data['name'] = 'Alersander'; $data['phone'] = '+7 000 123 45 67'; $data['city'] = 'St. Petersburg';
$STH = $DBH->prepare("INSERT INTO users (name, phone, city) values (:name, :phone, :city)"); $STH->execute($data);

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

Для выборки с произвольными параметрами тоже будем использовать метод $DBH->prepare.

Данные выборки можно получить с помощью методов:

$STH->fetch — выбирает следующую строку и возвращает в необходимом виде.
$STH->fetchAll — возвращает массив всех выбранных строк.
$STH->fetchObject — выбирает следующую строку и возвращает ее как объект.

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

Методу $STH->fetch можно указать, в каком виде нам нужно предоставить результат.

  • PDO::FETCH_BOTH (по умолчанию) — возвращает числовой и ассоциативный массив;
  • PDO::FETCH_ASSOC — возвращает массив ассоциативный с названиями столбцов;
  • PDO::FETCH_NUM — возвращает массив числовыми ключами в виде порядковых номеров столбцов;
  • PDO::FETCH_OBJ — возвращает анонимный объект со свойствами, соответствующими именам столбцов;
  • PDO::FETCH_BOUND — присваивает значения столбцов соответствующим переменным, заданным с помощью метода $STH->bindColumn();
  • PDO::FETCH_CLASS — присваивает значения столбцов соответствующим свойствам указанного класса. Если для какого-то столбца свойства нет, оно будет создано;
  • PDO::FETCH_INTO — обновляет существующий экземпляр указанного класса;
  • PDO::FETCH_LAZY — объединяет в себе PDO::FETCH_BOTH и PDO::FETCH_OBJ;

$STH = $DBH->prepare("SELECT name, phone, city FROM users"); $STH->execute();
while($res = $STH->fetch(PDO::FETCH_ASSOC)) { echo $res['name']; }
$STH = $DBH->prepare("SELECT name, phone, city FROM users"); $STH->execute();
while($res = $STH->fetch(PDO::FETCH_ OBJ)) { echo $res->name; }

Закрытие соединения и освобождение буфера запроса

В PDO нет специальных методов для этих целей. Закрыть соединение с базой данных можно путем переопределения переменных.

$DBH = null; $STH = null;

Полезные методы

$DBH->lastInsertId() — возвращает id последней вставленной записи.

$DBH->query(string) — экранирует специальные символы в строковых данных таким образом, что их становится безопасно использовать в запросах.

$STH->rowCount() — возвращает количество затронутых записей последним запросом.

Обработка ошибок PDO

Лично мне не нравится, что если я не заключаю все запросы в блок try/catch, то PDO выкидывает Fatal error со всеми интимными подробностями моего запроса.

В промышленном приложении заключать каждый запрос в блок try/catch, это идиотизм!

Поэтому я поступил следующим образом.
Мы немного расширим классы PDO и PDOStatement.

class DB extends PDO { public $error = false; // выводить сообщения об ошибках на экран? (true/false)
public function __construct($dsn, $username='', $password='', $driver_options=array()) { try { parent::__construct($dsn, $username, $password, $driver_options);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DBStatement', array($this)));
$this->query("SET NAMES 'cp1251'"); } catch(PDOException $e) { echo "Произошла ошибка в работе с базой данных..."; exit(); } }
public function prepare($sql, $driver_options=array()) { try { return parent::prepare($sql, $driver_options); } catch(PDOException $e) { $this->error($e->getMessage()); } }
public function query($sql) { try { return parent::query($sql); } catch(PDOException $e) { $this->error($e->getMessage()); } }
public function exec($sql) { try { return parent::exec($sql); } catch(PDOException $e) { $this->error($e->getMessage()); } }
public function error($msg) { if($this->error) { echo $msg; } else { echo "Произошла ошибка в работе с базой данных..."; }
exit(); } }
class DBStatement extends PDOStatement { protected $DBH;
protected function __construct($DBH) { $this->DBH = $DBH; }
public function execute($data=array()) { try { return parent::execute($data); } catch(PDOException $e) { $this->DBH->error($e->getMessage()); } } }

Немного подробнее…
Как видите, я реализую свои два класса DB и DBStatement, наследуя классы PDO и PDOStatement. Классы реализуют все необходимые мне для работы над ошибками методы, которые обрабатываются блоком try/catch.

$DBH = new DB("mysql:host=$host;dbname=$dbname", $user, $pass); $DBH->error = true; // Для отладки выводим сообщения об ошибках на экран.
$STH = $DBH->prepare("SELEC *FROM users"); $STH->execute($data);

Как видите, я совершил опечатку в операторе и могу получить всю необходимую информацию об этой ошибке.

«SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘SELEC * FROM users’ at line 1»

Расширение функционала

Также бывает полезно расширить функционал PDO. Например, часто требуется получить количество записей в таблице.

Стандартными методами это можно делать следующим образом:

$data = array(); $data['user_id'] = 1;
$STH = $DBH->prepare("SELECT COUNT(*) as count FROM users WHERE user_id=:user_id"); $STH->execute($data);
echo $STH->fetch(PDO::FETCH_OBJ)->count;

Не слишком удобно. Поэтому реализуем в нашем классе методом count.

class DB extends PDO { …
public function count($sql, $data) { $res = $this->prepare($sql); $res->execute($data);
return $res->fetch(PDO::FETCH_OBJ); }
…
}

Получим количество записей

$DBH = new DB("mysql:host=$host;dbname=$dbname", $user, $pass); echo $DBH->count("SELECT COUNT(*) as count FROM users WHERE user_id=:user_id", array('user_id'=>'1'))->count;

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

$res = $DBH->prepare("SELECT *FROM users WHERE id_user=:id_user")->execute(array('id_user'=>'1'))->fetch(PDO::FETCH_ASSOC);
echo $res['name'];
// или так
echo $DBH->prepare("SELECT *FROM users WHERE id_user=:id_user")->execute(array('id_user'=>'1'))->fetch(PDO::FETCH_OB)->name;

Метод execute возвращает бесполезное логическое значение об успехе операции, а не объект DBStatement.

Допиливаем свой метод execute.

public function execute($data=array()) { try { parent::execute($data); return $this; } catch(PDOException $e) { $this->DBH->error($e->getMessage()); } }

Проверяем результат.

echo $DBH->prepare("SELECT COUNT(*) as count FROM users WHERE city=:city")->execute(array('city'=>'St. Petersburg'))->fetch(PDO::FETCH_OBJ)->count;

В качестве заключения

Это далеко не все возможности расширения PDO, за более подробной информацией вы можете обратиться http://php.net/manual/ru/book.pdo.php.

#php

Комментарии (0)

Добавить комментарий

Войдите, чтобы написать о чем-нибудь...
Вход Регистрация
Web.onRails
Здесь вы можете спросить или написать обо всём, что касается Веб-разработки.
написать о чем-нибудь...
Метки:
Лучшее
[52]
16 Окт 2011, 15:38
Вывести все элементы POST
[просмотров 13449]
[74]
31 мая 2011, 11:48
Python проверка существования переменной
[просмотров 9220]
[4]
10 Окт 2018, 15:33
Как запретить просмотр сайта по IP?
[просмотров 8834]
[100]
19 Дек 2014, 16:16
User-agent для Internet Explorer 11
[просмотров 8158]
[124]
21 Июл 2011, 14:04
Python Imaging Library (PIL)
[просмотров 6816]
[58]
29 мая 2012, 12:08
Узнать версию PHP из командной строки
[просмотров 4939]
[315]
16 Июл 2011, 20:03
Python работа с MySQL
[просмотров 4902]

Вести с Хабра