Почему следует использовать PDO для доступа к базам данных?

Многие программисты PHP изучили и успешно используют доступ к базам данных расширения mysql или mysqli. Однако в PHP начиная с версии 5.1 имеется лучший способ. PHP Data Objects (PDO) обеспечивает методы для подготовки выражений и работы с объектами, которые могут сделать Вашу работу более продуктивной!

Введение в PDO

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

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

Механизм использования PDO

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

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

# Закрываем соединение  
$DBH = null;  

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

Исключения и 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. Ниже приводятся 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] - во второй, и так далее. Однако, если массив проиндексирован в другом порядке, то такая операция будет выполняться некорректно. Вам нужно следить за соответствием порядка следования шаблонов и порядком расположения данных в массиве.

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

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

# Первый аргумент функции - имя именованного шаблона  
# Именованный шаблон всегда начинается с двоеточия
$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() всегда вызывается дескриптором базы данных (а не дескриптором состояния) и возвращает значение автоматически увеличивающегося идентификатора последней вставленной строки для данного соединения.

$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 "Нет строк соответствующих запросу.";  
    }  
}

Надеюсь, что урок Вам понравился!

Данный урок подготовлен для вас командой сайта ruseller.com
Источник урока: www.net.tutsplus.com
Перевел: Сергей Фастунов
Урок создан: 23 Июня 2010
Просмотров: 75834
Правила перепечатки


5 последних уроков рубрики "Разное"

или авторизуйтесь, чтобы добавлять комментарии, оценивать уроки и сохранять их в личном кабинете
  • 24 Июня 2010 01:46
    Виктор
    Спасибо, завтра будем тестить
  • 24 Июня 2010 06:00
    Alexxhub
    Отличная штука, всем юзать, осваиваю уже с неделю наверное
  • 24 Июня 2010 06:39
    Сумрак
    УРРРРАААА!!! Новый и такой долгожданный урок по PHP! Да еще и такой классный!)))
  • 24 Июня 2010 11:04
    FeroDaR
    Спасибо. Надо будет обезательно попробывать.
    • 11 Марта 2012 15:33
      locord
      ага
  • 24 Июня 2010 15:56
    Mihakob
    а как использовать этот способ для WordPress?
  • 24 Июня 2010 16:53
    zura
    А чем он лучше mysqli?
  • 27 Июня 2010 15:48
    zura
    А как тут фильтровать данные? mysql_real_escape_string не работает
    • 8 Апреля 2012 07:46
      Alfaix
      Эта функция не нужна. PDO сам экранирует символы, которые могут быть восприняты БД как SQL-запрос.
  • 29 Сентября 2010 12:24
    turin
    Bug #40822 pdo rowcount bug Status: Closed http://bugs.php.net/40822 Ы - для того чтоб ЗАПОСТИТЬ !
  • 29 Сентября 2010 12:24
    turin
    PDO - Это лишь иной способ обращения к Базам данных...
  • 18 Октября 2010 14:53
    sabotagnik
    Здравствуйте! Недавно начал знакомство с PDO и не могу понять, как создавать базы данных с его помощью (CREATE BATABASE `dbanme`). Возможно ли это вообще. Или все же придется создавать базу средствами php-mysql? Подскажите, кто знает.
  • 23 Июля 2011 21:37
    Антон Фурс
    просто делаете запрос с CREATE DATABASE и все, какие проблемы то.
  • 25 Августа 2011 01:26
    Alexej28
    Тоже недавно начал пользоваться этим способом, пока конечно разницы не заметно, потому что каникулы и посещаемость низкая, думаю с сентября увижу разницу. К сожалению в уроке не было указано к примеру на то как исправить косяк с кодировками, у меня выходили вопросы. Решение простое, как при обычном способе.
     $db = new PDO("mysql:host=$hostname;dbname=$DBName", $username, $password);	$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); $db->query("SET NAMES cp1251");
    Просто подставляем нужную кодировку.
  • 26 Августа 2011 20:28
    Alexej28
    Появился у меня ещё один вопрос, а возможно ли как в обычном способе одним запросом делать выборку из двух разных Баз Данных? Если да то кто знает напишите.
  • 12 Ноября 2011 12:18
    Teskuroi
    Это выражение дает ошибку:
    $STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
    $STH->execute($data); 
    Нужно писать:
    $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
    $STH->execute($data);
    В уроке таких мест много, исправьте, пожалуйста.
    • 24 Января 2014 17:10
      PeterLS
      Будем пробывать раз уж в популярность вошел)
  • 9 Июля 2014 19:04
    wot_t_50_2
    Чё-то не особо понятная статья, сумбурно как-то, местами непонятно что где
^ Наверх ^