Многие программисты PHP изучили и успешно используют доступ к базам данных расширенияmysql или mysqli. Однако в PHP начиная с версии 5.1 имеется лучший способ. PHP Data Objects (PDO) обеспечивает методы для подготовки выражений и работы с объектами, которые могут сделать Вашу работу более продуктивной!
Введение в PDO
"PDO — PHP Data Objects — это уровень для доступа к базам данных, который обеспечивает унифицированные методы для доступа к различным базам данных."
Он не зависит от специфического синтаксиса базы данных и позволяет легко переключаться на другой тип баз данных и платформу простым изменением строки подключения в большинстве случаев.
Данный урок не является описанием процесса работы с 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();
, указывающее количество строк, которые обрабатываются операцией. В последней версии PDO, в соответствии с отчетом об ошибках(http://bugs.php.net/40822) данный метод не работает с выражениями
$sql = "SELECT COUNT(*) FROM folks"; if ($STH = $DBH->query($sql)) { # Проверка количества строк if ($STH->fetchColumn() > 0) { # Здесь должен быть код SELECT } else { echo "Нет строк соответствующих запросу."; } }
Надеюсь, что урок Вам понравился!