Компьютерный мастер

Pdo ассоциативный массив. Почему следует использовать PDO для доступа к базам данных? PDO Соединение с БД


3 июня 2018 Андрей Чернышов Перевод Туториал 2025 0

PDO является акронимом для PHP Data Objects: это PHP расширение для работы с базами данных используя объекты. Одно из его преимуществ лежит в том, что оно не привязано напрямую к определенной базе данных: его интерфейс позволяет получить доступ к нескольким разным средам, включая: MySQL, SQLite, PostgreSQL, Microsoft SQL Server.

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

Создание тестовой базы данных и таблицы

В первую очередь, мы создадим базу данных:

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

Мы выдали пользователю testuser все привилегии в базе данных solar_system , используя testpassword для пароля. Теперь давайте создадим таблицу и заполним её какой-нибудь информацией:

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 (Data Source Name)

Теперь, когда у нас есть база данных, мы должны задать DSN . DSN расшифровывается как Data Source Name , и является набором информации, необходимой для подключения к базе данных, DSN имеет форму строки. Синтаксис отличается в зависимости от базы данных, подключение к которой требуется, но так как мы используем MySQL/MariaDB, нам нужно задать следующие:

  • Тип драйвера, используемого для подключения;
  • Имя компьютера-хоста, на котором запущена база данных;
  • Порт для подключения (необязательно);
  • Название базы данных;
  • Кодировка (необязательно).

Формат строки в нашем случае будет таким (мы будем хранить его в переменной $dsn):

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

В первую очередь мы задали database prefix или префикс базы данных. В этом случае, так как мы подключаемся к базе данных типа MySQL/MariaDB, мы используем mysql . Затем мы отделили префикс от остальной строки двоеточием и каждая последующая секция отделена от остальных точкой с запятой.

В следующих двух секциях мы задали hostname , на котором запущена база данных и port используемый для подключения. Если порт не указан, использован будет порт по умолчанию, в данном случае это 3306 . Сразу после database name указывается charset .

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

Теперь, когда наш DSN готов, мы приступим к созданию PDO object . Конструктор 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 повидения при ошибках

Давайте взглянем на некоторые опции доступные для PDO::ATTR_ERRMODE . Эти опции крайне важны, потому что они определяют поведение PDO в случае возникновения ошибок. Возможные опции:

PDO::ERRMODE_SILENT

Опция по умолчанию. PDO просто выдаст код ошибки и сообщение об ошибке. Их можно будет получить используя методы errorCode() и errorInfo() .

PDO::ERRMODE_EXCEPTION

Эта опция, на мой взгляд, рекомендуема для использования. С её помощью, помимо выдачи кода ошибки и информации, PDO выдаст исключение PDOException , которое прервет ход выполнения скрипта, а ещё она полезна при PDO transactions (их мы рассмотрим чуть позже).

PDO::ERRMODE_WARNING

С этой опцией, PDO выдаст код ошибки и сообщение о ней, как и при PDO::ERRMODE_SILENT , но еще и покажет предупреждение WARNING , которое не прерывает работу скрипта.

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

Еще одна важная настройка, регулируется с помощью константы PDO::DEFAULT_FETCH_MODE . Она позволяет настроить по умолчанию работу метода fetch() , который будет использоваться для получения результатов запроса. Вот самые часто используемые опции:

PDO::FETCH_BOTH

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

$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

Используя PDO::FETCH_NUM константу мы получим 0-indexed array:

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

PDO::FETCH_COLUMN

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

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

В результате:

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

PDO::FETCH_KEY_PAIR

Эта константа полезна, когда нужно получить значения из двух столбцов. Метод fetchAll() вернет результаты в виде ассоциативного массива. В этом массиве, данные из первого столбца будут указаны в форме ключей, а из второго – в качестве значений:

$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 , будет создан anonymous object за каждый полученный ряд. Его (публичные) свойства будут названы также, как и столбцы, а результаты запроса будут использованы в качестве значений. Использование этого метода для того же запроса, что и выше, приведет к следующему результату:

$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");

Мы задаём константу PDO::FETCH_CLASS как первый аргумент метода setFetchMode() и название класса, использовано для создания объекта (в нашем случае «Planet») – вторым аргументом. Теперь запускаем код:

$planet = $stmt->fetch();

Должен получиться объект Planet:

Var_dump($planet); Planet Object ( => earth => blue)

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

Назначение характеристик после создания объекта

Класс «Planet», не обладал никаким определенным конструктором, так что проблем с назначением характеристик не возникло; но что если у класса есть конструктор, в котором характеристики задаются и изменяются? Так как значения назначены до запуска конструктора, они будут перезаписаны.

PDO помогает предоставить константу 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 name и сolor . Эти аргументы имеют базовые значения: 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();

А теперь рассмотрим объект Planet и проверим, какие значения соответствуют его характеристикам:

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 ($planet = $stmt->fetch()) { // Что-то делам с результатами }

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

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

PDO::FETCH_INTO

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

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

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

Прямые запросы

Для использования прямых запросов существует два главных метода: query() и exec() . Первый из них создает объект PDOStatemnt , доступ к которому можно получить через методы fetch() или fetchAll() : если вы используете их в случаях, когда таблица не меняется, таких как SELECT .

Второй метод, взамен, возвращает номер ряда, который был изменен запросом: мы используем его в случаях, которые заменяют ряды, таких как INSERT , DELETE или UPDATE . Прямые запросы должны быть использованы, только в случаях отсутствия переменных в запросах, и в безопасности метода нет никаких сомнений.

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

PDO также поддерживает запросы исполняемые в два шага, подготовленные: они полезны, когда в запросах есть переменные, и более безопасны в целом, поскольку метод prepare() сделает все необходимые действия за нас. Давайте взглянем, как используются переменные. Представьте, что мы хотим вставить характеристики планеты в таблицу Planets . Для начала, подготовим запрос:

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

Как и говорилось ранее, мы используем метод prepare() который использует SQL запрос в качестве аргумента, используя временные значения для переменных. Временные значения могут быть двух типов: позиционные и именные.

Позиционные

Используя? позиционные временные значения, код получается более краткий, но мы должны задать данные, которые будут вставлены, в том же порядке, что и имена столбцов, в массиве представленном как аргумент метода 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() оба могут быть использованы для запросов, которые изменяют или просто получают информацию из базы данных. В первом случае, мы используем fetch методы перечисленные сверху для получения информации, а во втором – используя метод rowCount() .

Методы bindValue() и bindParam()

Для предоставления значений, которые будут вставлены в запрос, могут также использоваться методы bindValue() и bindParam() . Первый привязывает значение заданной переменной к позиционному или именному временному значению, использованному при подготовке запроса. Взяв за пример предыдущий случай, мы сделаем:

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

Мы привязываем значение $planet->name к временному значению:name . Замете, что используя оба метода bindValue() и bindParam() мы можем также задать тип переменной, как третий аргумент, используя подходящую PDO константу, в этом случае PDO::PARAM_STR .

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

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

Мы привязали переменную, а не её значение $planet->name к:name ! Как сказано выше, замена произойдет только при запуске метода execute() , так что временное значение будет заменено на значение переменной в тот момент.

PDO Транзакции

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

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

$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() в объекте PDO отключает autocommit запроса, затем запросы запускаются в необходимом порядке. В этот момент, если не возникает исключение PDOException запросы автоматически пропускаются через метод commit() , в противном случае – через метод rollBack() транзакции отменяются и autocommit восстанавливается.

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

  • Перевод

Множество PHP-разработчиков привыкли использовать для работы с базами данных расширения mysql и mysqli. Но с версии 5.1 в PHP существует более удобный способ - PHP Data Objects . Этот класс, сокращенно именуемый PDO, предоставляет методы для работы с объектами и prepared statements , которые заметно повысят вашу продуктивность!

Введение в PDO«PDO – PHP Data Objects – это прослойка, которая предлагает универсальный способ работы с несколькими базами данных.»
Заботу об особенностях синтаксиса различных СУБД она оставляет разработчику, но делает процесс переключения между платформами гораздо менее болезненным. Нередко для этого требуется лишь изменить строку подключения к базе данных.


Эта статья написана для людей, которые пользуются mysql и mysqli, чтобы помочь им в переходе на более мощный и гибкий 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)
Впрочем, не все из них есть на вашем сервере. Увидеть список доступных драйверов можно так:
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-операции и использовать механизм исключений (об этом чуть дальше).

$DBH расшифровывается как «database handle» и будет использоваться на протяжении всей статьи.

Закрыть любое подключение можно путем переопределения его переменной в null.
# закрывает подключение $DBH = null;
Больше информации по теме отличительных опций разных СУБД и методах подключения к ним можно найти на php.net .

Исключения и 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);
Но стоит заметить, что ошибка при попытке соединения будет всегда вызывать исключение.PDO::ERRMODE_SILENT Это режим по умолчанию. Примерно то же самое вы, скорее всего, используете для отлавливания ошибок в расширениях mysql и mysqli. Следующие два режима больше подходят для DRY программирования.PDO::ERRMODE_WARNING Этот режим вызовет стандартный Warning и позволит скрипту продолжить выполнение. Удобен при отладке.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")->execute(); } catch(PDOException $e) { echo "Хьюстон, у нас проблемы."; file_put_contents("PDOErrors.txt", $e->getMessage(), FILE_APPEND); }
В SQL-выражении есть синтаксическая ошибка, которая вызовет исключение. Мы можем записать детали ошибки в лог-файл и человеческим языком намекнуть пользователю, что что-то случилось.Insert и Update Вставка новых и обновление существующих данных являются одними из наиболее частых операций с БД. В случае с PDO этот процесс обычно состоит из двух шагов. (В следующей секции все относится как к UPDATE, так и INSERT)


Тривиальный пример вставки новых данных:
# STH означает "Statement Handle" $STH = $DBH->prepare("INSERT INTO folks (first_name) values ("Cathy")"); $STH->execute();
Вообще-то можно сделать то же самое одним методом exec(), но двухшаговый способ дает все преимущества prepared statements. Они помогают в защите от SQL-инъекций, поэтому имеет смысл их использовать даже при однократном запросе.Prepared StatementsИспользование prepared statements укрепляет защиту от SQL-инъекций.
Prepared statement - это заранее скомпилированное SQL-выражение, которое может быть многократно выполнено путем отправки серверу лишь различных наборов данных. Дополнительным преимуществом является невозможность провести SQL-инъекцию через данные, используемые в placeholder’ах.

Ниже находятся три примера prepared statements.
# без placeholders - дверь SQL-инъекциям открыта! $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values ($name, $addr, $city)"); # безымянные placeholders $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)"); # именные placeholders $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)");
Первый пример здесь лишь для сравнения, его стоит избегать. Разница между безымянными и именными placeholder’ами в том, как вы будете передавать данные в prepared statements.

Безымянные placeholder’ы # назначаем переменные каждому placeholder, с индексами от 1 до 3 $STH->bindParam(1, $name); $STH->bindParam(2, $addr); $STH->bindParam(3, $city); # вставляем одну строку $name = "Daniel" $addr = "1 Wicked Way"; $city = "Arlington Heights"; $STH->execute(); # вставляем еще одну строку, уже с другими данными $name = "Steve" $addr = "5 Circle Drive"; $city = "Schaumburg"; $STH->execute();
Здесь два шага. На первом мы назначаем всем placeholder’ам переменные (строки 2-4). Затем назначаем этим переменным значения и выполняем запрос. Чтобы послать новый набор данных, просто измените значения переменных и выполните запрос еще раз.

Если в вашем SQL-выражении много параметров, то назначать каждому по переменной весьма неудобно. В таких случаях можно хранить данные в массиве и передавать его:
# набор данных, которые мы будем вставлять $data = array("Cathy", "9 Dark and Twisty Road", "Cardiff"); $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)"); $STH->execute($data);
$data вставится на место первого placeholder’а, $data - на место второго, и т.д. Но будьте внимательны: если ваши индексы сбиты, это работать не будет.

Именные placeholder’ы # первым аргументом является имя placeholder’а # его принято начинать с двоеточия # хотя работает и без них $STH->bindParam(":name", $name);
Здесь тоже можно передавать массив, но он должен быть ассоциативным. В роли ключей должны выступать, как можно догадаться, имена placeholder’ов.
# данные, которые мы вставляем $data = array("name" => "Cathy", "addr" => "9 Dark and Twisty", "city" => "Cardiff"); $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)"); $STH->execute($data);
Одним из удобств использования именных placeholder’ов является возможность вставки объектов напрямую в базу данных, если названия свойств совпадают с именами параметров. Вставку данных, к примеру, вы можете выполнить так:
# класс для простенького объекта 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("Cathy","9 Dark and Twisty","Cardiff"); # а тут самое интересное $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)"); $STH->execute((array)$cathy);
Преобразование объекта в массив при execute() приводит к тому, что свойства считаются ключами массива.Выборка данных

Данные можно получить с помощью метода ->fetch(). Перед его вызовом желательно явно указать, в каком виде они вам требуются. Есть несколько вариантов:
  • PDO::FETCH_ASSOC: возвращает массив с названиями столбцов в виде ключей
  • PDO::FETCH_BOTH (по умолчанию): возвращает массив с индексами как в виде названий стобцов, так и их порядковых номеров
  • 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.
# поскольку это обычный запрос без placeholder’ов, # можно сразу использовать метод query() $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->addr = preg_replace("//", "x", $this->addr); $this->other_data = $other; } }
При создании объекта все латинские буквы в нижнем регистре должны замениться на 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 Rosebud’, то на выходе получится ’5 Rxxxxxx’.

Конечно, иногда будет требоваться, чтобы конструктор вызывался ПЕРЕД присваиванием значений. 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"));
Можно даже передавать разные аргументы каждому объекту:
$i = 0; while($rowObj = $STH->fetch(PDO::FETCH_CLASS, "secret_person", array($i))) { // что-то делаем $i++; }

Другие полезные методы Хотя эта статья не может (и не пытается) охватить все аспекты работы с PDO (это огромный модуль!), оставить без упоминания следующие несколько функций нельзя.
$DBH->lastInsertId();
Метод ->lastInsertId() возвращает id последней вставленной записи. Стоит заметить, что он всегда вызывается у объекта базы данных (в статье он именуется $DBH), а не объекта с выражением ($STH).
$DBH->exec("DELETE FROM folks WHERE 1"); $DBH->exec("SET time_zone = "-8:00"");
Метод ->exec() используется для операций, которые не возвращают никаких данных, кроме количества затронутых ими записей.
$safe = $DBH->quote($unsafe);
Метод ->quote() ставит кавычки в строковых данных таким образом, что их становится безопасно использовать в запросах. Пригодится, если вы не используете prepared statements.
$rows_affected = $STH->rowCount();
Метод ->rowCount() возвращает количество записей, которые поучаствовали в операции. К сожалению, эта функция отказывалась работать с SELECT-запросами вплоть до PHP 5.1.6. Если обновить версию PHP не представляется возможным, количество записей можно получить так:
$sql = "SELECT COUNT(*) FROM folks"; if ($STH = $DBH->query($sql)) { # проверяем количество записей if ($STH->fetchColumn() > 0) { # делаем здесь полноценную выборку, потому что данные найдены! } else { # выводим сообщение о том, что удовлетворяющих запросу данных не найдено } } Заключение Надеюсь, этот материал поможет кому-то из вас осуществить миграцию с расширений mysql и mysqli.
  • Перевод

Множество PHP-разработчиков привыкли использовать для работы с базами данных расширения mysql и mysqli. Но с версии 5.1 в PHP существует более удобный способ - PHP Data Objects . Этот класс, сокращенно именуемый PDO, предоставляет методы для работы с объектами и prepared statements , которые заметно повысят вашу продуктивность!

Введение в PDO«PDO – PHP Data Objects – это прослойка, которая предлагает универсальный способ работы с несколькими базами данных.»
Заботу об особенностях синтаксиса различных СУБД она оставляет разработчику, но делает процесс переключения между платформами гораздо менее болезненным. Нередко для этого требуется лишь изменить строку подключения к базе данных.


Эта статья написана для людей, которые пользуются mysql и mysqli, чтобы помочь им в переходе на более мощный и гибкий 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)
Впрочем, не все из них есть на вашем сервере. Увидеть список доступных драйверов можно так:
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-операции и использовать механизм исключений (об этом чуть дальше).

$DBH расшифровывается как «database handle» и будет использоваться на протяжении всей статьи.

Закрыть любое подключение можно путем переопределения его переменной в null.
# закрывает подключение $DBH = null;
Больше информации по теме отличительных опций разных СУБД и методах подключения к ним можно найти на php.net .

Исключения и 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);
Но стоит заметить, что ошибка при попытке соединения будет всегда вызывать исключение.PDO::ERRMODE_SILENT Это режим по умолчанию. Примерно то же самое вы, скорее всего, используете для отлавливания ошибок в расширениях mysql и mysqli. Следующие два режима больше подходят для DRY программирования.PDO::ERRMODE_WARNING Этот режим вызовет стандартный Warning и позволит скрипту продолжить выполнение. Удобен при отладке.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")->execute(); } catch(PDOException $e) { echo "Хьюстон, у нас проблемы."; file_put_contents("PDOErrors.txt", $e->getMessage(), FILE_APPEND); }
В SQL-выражении есть синтаксическая ошибка, которая вызовет исключение. Мы можем записать детали ошибки в лог-файл и человеческим языком намекнуть пользователю, что что-то случилось.Insert и Update Вставка новых и обновление существующих данных являются одними из наиболее частых операций с БД. В случае с PDO этот процесс обычно состоит из двух шагов. (В следующей секции все относится как к UPDATE, так и INSERT)


Тривиальный пример вставки новых данных:
# STH означает "Statement Handle" $STH = $DBH->prepare("INSERT INTO folks (first_name) values ("Cathy")"); $STH->execute();
Вообще-то можно сделать то же самое одним методом exec(), но двухшаговый способ дает все преимущества prepared statements. Они помогают в защите от SQL-инъекций, поэтому имеет смысл их использовать даже при однократном запросе.Prepared StatementsИспользование prepared statements укрепляет защиту от SQL-инъекций.
Prepared statement - это заранее скомпилированное SQL-выражение, которое может быть многократно выполнено путем отправки серверу лишь различных наборов данных. Дополнительным преимуществом является невозможность провести SQL-инъекцию через данные, используемые в placeholder’ах.

Ниже находятся три примера prepared statements.
# без placeholders - дверь SQL-инъекциям открыта! $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values ($name, $addr, $city)"); # безымянные placeholders $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)"); # именные placeholders $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)");
Первый пример здесь лишь для сравнения, его стоит избегать. Разница между безымянными и именными placeholder’ами в том, как вы будете передавать данные в prepared statements.

Безымянные placeholder’ы # назначаем переменные каждому placeholder, с индексами от 1 до 3 $STH->bindParam(1, $name); $STH->bindParam(2, $addr); $STH->bindParam(3, $city); # вставляем одну строку $name = "Daniel" $addr = "1 Wicked Way"; $city = "Arlington Heights"; $STH->execute(); # вставляем еще одну строку, уже с другими данными $name = "Steve" $addr = "5 Circle Drive"; $city = "Schaumburg"; $STH->execute();
Здесь два шага. На первом мы назначаем всем placeholder’ам переменные (строки 2-4). Затем назначаем этим переменным значения и выполняем запрос. Чтобы послать новый набор данных, просто измените значения переменных и выполните запрос еще раз.

Если в вашем SQL-выражении много параметров, то назначать каждому по переменной весьма неудобно. В таких случаях можно хранить данные в массиве и передавать его:
# набор данных, которые мы будем вставлять $data = array("Cathy", "9 Dark and Twisty Road", "Cardiff"); $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)"); $STH->execute($data);
$data вставится на место первого placeholder’а, $data - на место второго, и т.д. Но будьте внимательны: если ваши индексы сбиты, это работать не будет.

Именные placeholder’ы # первым аргументом является имя placeholder’а # его принято начинать с двоеточия # хотя работает и без них $STH->bindParam(":name", $name);
Здесь тоже можно передавать массив, но он должен быть ассоциативным. В роли ключей должны выступать, как можно догадаться, имена placeholder’ов.
# данные, которые мы вставляем $data = array("name" => "Cathy", "addr" => "9 Dark and Twisty", "city" => "Cardiff"); $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)"); $STH->execute($data);
Одним из удобств использования именных placeholder’ов является возможность вставки объектов напрямую в базу данных, если названия свойств совпадают с именами параметров. Вставку данных, к примеру, вы можете выполнить так:
# класс для простенького объекта 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("Cathy","9 Dark and Twisty","Cardiff"); # а тут самое интересное $STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)"); $STH->execute((array)$cathy);
Преобразование объекта в массив при execute() приводит к тому, что свойства считаются ключами массива.Выборка данных

Данные можно получить с помощью метода ->fetch(). Перед его вызовом желательно явно указать, в каком виде они вам требуются. Есть несколько вариантов:
  • PDO::FETCH_ASSOC: возвращает массив с названиями столбцов в виде ключей
  • PDO::FETCH_BOTH (по умолчанию): возвращает массив с индексами как в виде названий стобцов, так и их порядковых номеров
  • 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.
# поскольку это обычный запрос без placeholder’ов, # можно сразу использовать метод query() $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->addr = preg_replace("//", "x", $this->addr); $this->other_data = $other; } }
При создании объекта все латинские буквы в нижнем регистре должны замениться на 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 Rosebud’, то на выходе получится ’5 Rxxxxxx’.

Конечно, иногда будет требоваться, чтобы конструктор вызывался ПЕРЕД присваиванием значений. 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"));
Можно даже передавать разные аргументы каждому объекту:
$i = 0; while($rowObj = $STH->fetch(PDO::FETCH_CLASS, "secret_person", array($i))) { // что-то делаем $i++; }

Другие полезные методы Хотя эта статья не может (и не пытается) охватить все аспекты работы с PDO (это огромный модуль!), оставить без упоминания следующие несколько функций нельзя.
$DBH->lastInsertId();
Метод ->lastInsertId() возвращает id последней вставленной записи. Стоит заметить, что он всегда вызывается у объекта базы данных (в статье он именуется $DBH), а не объекта с выражением ($STH).
$DBH->exec("DELETE FROM folks WHERE 1"); $DBH->exec("SET time_zone = "-8:00"");
Метод ->exec() используется для операций, которые не возвращают никаких данных, кроме количества затронутых ими записей.
$safe = $DBH->quote($unsafe);
Метод ->quote() ставит кавычки в строковых данных таким образом, что их становится безопасно использовать в запросах. Пригодится, если вы не используете prepared statements.
$rows_affected = $STH->rowCount();
Метод ->rowCount() возвращает количество записей, которые поучаствовали в операции. К сожалению, эта функция отказывалась работать с SELECT-запросами вплоть до PHP 5.1.6. Если обновить версию PHP не представляется возможным, количество записей можно получить так:
$sql = "SELECT COUNT(*) FROM folks"; if ($STH = $DBH->query($sql)) { # проверяем количество записей if ($STH->fetchColumn() > 0) { # делаем здесь полноценную выборку, потому что данные найдены! } else { # выводим сообщение о том, что удовлетворяющих запросу данных не найдено } } Заключение Надеюсь, этот материал поможет кому-то из вас осуществить миграцию с расширений mysql и mysqli.

На днях решил разобраться с 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 (PHP Data Objects) - расширение PHP, которое реализует взаимодействие с базами данных при помощи объектов. Профит в том, что отсутствует привязка к конкретной системе управления базами данных. PDO поддерживает СУБД: MySQL, PostgreSQL, SQLite, Oracle, Microsoft SQL Server и другие.

Почему стоит использовать PDO

Функции mysql в PHP для работы с БД давно уже устарели, на сегодняшний день желательно использовать mysqli или PDO (PHP Data Objects). Кроме того, mysqli - эта библиотека, которая по большому счёту, не предназначена для использования напрямую в коде. Она может послужить хорошим строительным материалом для создания библиотеки более высокого уровня. При работе с mysqli следует также помнить об обеспечении безопасности вашего приложения, в частности о защите от SQL-инъекций. В случае использования PDO (с его подготовленными запросами), такая защита идёт уже "из коробки", главное правильно применить необходимые методы.

Тестовая база данных с таблицей // Из консоли Windows mysql> CREATE DATABASE `pdo-test` CHARACTER SET utf8 COLLATE utf8_general_ci; USE pdo-test; CREATE TABLE categories (id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id), name VARCHAR(255) NOT NULL); INSERT INTO `categories` (`name`) VALUES ("Ноутбуки и планшеты"), ("Компьютеры и периферия"), ("Комплектующие для ПК"), ("Смартфоны и смарт-часы"), ("Телевизоры и медиа"), ("Игры и приставки"), ("Аудиотехника"), ("Фото-видеоаппаратура"), ("Офисная техника и мебель"), ("Сетевое оборудование"), ("Крупная бытовая техника"), ("Товары для кухни"), ("Красота и здоровье"), ("Товары для дома"), ("Инструменты"), ("Автотовары"); Установка PDO // Установка в Linux sudo apt update sudo apt install php7.2-mysql sudo apt-get install pdo-mysql // В php.ini добавить extension=pdo.so extension=pdo_mysql.so // На Windows, как правило драйвер уже установлен, нужно просто проверить включен ли он в php.ini extension=php_pdo_mysql.dll Проверить доступные драйвера print_r(PDO::getAvailableDrivers()); Соединение с базой данных

Соединения устанавливаются автоматически при создании объекта PDO от его базового класса.

// Пример #1. Простое подключение $db = new PDO("mysql:host=localhost;dbname=pdo", "root", "password");

При ошибке подключения PHP выдаст ошибку:

Fatal error: Uncaught PDOException: ... // Пример #2. Обработка ошибок подключения try { $dbh = new PDO("mysql:host=localhost;dbname=pdo", "root", "password"); } catch (PDOException $e) { print "Error!: " . $e->getMessage(); die(); }

В этом примере подключения мы используем конструкцию try...catch . Многие спорят о целесообразности её использования. Лично я использую try...catch , она мне не мешает.

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

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

  • Прямой - состоит из одного шага;
  • Подготовленный - состоит из двух шагов.
Прямые запросы
  • query() используется для операторов, которые не вносят изменения, например SELECT . Возвращает объект PDOStatemnt , из которого с помощью методов fetch() или fetchAll извлекаются результаты запроса. Можно его сравнить с mysql resource , который возвращала mysql_query() .
  • exec() используется для операторов INSERT, DELETE, UPDATE . Возвращает число обработанных запросом строк.

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

$stmt = $db->query("SELECT * FROM categories"); while ($row = $stmt->fetch()) { echo ""; print_r($row); }

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

Если же в запрос передаётся хотя бы одна переменная, то этот запрос в обязательном порядке должен выполняться только через подготовленные выражения. Что это значит? Это обычный SQL запрос, в котором вместо переменной ставится специальный маркер - плейсхолдер. PDO поддерживает позиционные плейсхолдеры (?), для которых важен порядок передаваемых переменных, и именованные (:name), для которых порядок не важен. Примеры:

$sql = "SELECT name FROM categories WHERE id = ?"; $sql = "SELECT name FROM categories WHERE name = :name";

Чтобы выполнить такой запрос, сначала его надо подготовить с помощью метода prepare() . Она также возвращает PDO statement , но ещё без данных. Чтобы их получить, надо исполнить этот запрос, предварительно передав в него наши переменные. Передать можно двумя способами: Чаще всего можно просто выполнить метод execute() , передав ему массив с переменными:

$stmt = $pdo->prepare("SELECT `name` FROM categories WHERE `id` = ?"); $stmt->execute([$id]); $stmt = $pdo->prepare("SELECT `name` FROM categories WHERE `name` = :name"); $stmt->execute(["name" => $name]);

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

$id = 1; $stmt = $db->prepare("SELECT * FROM categories WHERE `id` = ?"); $stmt->execute([$id]); $category = $stmt->fetch(PDO::FETCH_LAZY); echo ""; print_r($category);

ВАЖНО! Подготовленные запросы - основная причина использовать PDO, поскольку это единственный безопасный способ выполнения SQL запросов, в которых участвуют переменные.

Получение данных. Метод fetch()

Мы уже выше познакомились с методом fetch() , который служит для последовательного получения строк из БД. Этот метод является аналогом функции mysq_fetch_array() и ей подобных, но действует по-другому: вместо множества функций здесь используется одна, но ее поведение задается переданным параметром. В подробностях об этих параметрах будет написано в , а в качестве краткой рекомендации посоветую применять fetch() в режиме FETCH_LAZY

$id = 1; $stmt = $db->prepare("SELECT * FROM categories WHERE `id` = ?"); $stmt->execute([$id]); while ($row = $stmt->fetch(PDO::FETCH_LAZY)) { echo "Category name: ".$row->name; }

В этом режиме не тратится лишняя память, и к тому же к колонкам можно обращаться любым из трех способов - через индекс, имя, или свойство (через ->). Недостатком же данного режима является то, что он не работает с fetchAll()

Получение данных. Метод fetchColumn()

Также у PDO statement есть метод для получения значения единственной колонки. Очень удобно, если мы запрашиваем только одно поле - в этом случае значительно сокращается количество кода:

$id = 1; $stmt = $db->prepare("SELECT `name` FROM categories WHERE `id` = ?"); $stmt->execute([$id]); $name = $stmt->fetchColumn(); echo "Category name: ".$name;

Получение данных. Метод fetchAll() $data = $db->query("SELECT * FROM categories")->fetchAll(PDO::FETCH_ASSOC); foreach ($data as $k => $v){ echo "Category name: ".$v["name"]."
"; } PDO и оператор LIKE

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

$search = "комп"; $query = "SELECT * FROM categories WHERE `name` LIKE ?"; $params = ["%$search%"]; $stmt = $db->prepare($query); $stmt->execute($params); $data = $stmt->fetchAll(PDO::FETCH_ASSOC); $i = 1; foreach ($data as $category){ echo $i++ . ". " . $category["name"]."
"; }

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

$db = new PDO("mysql:host=localhost;dbname=pdo;charset=utf8", "root", "");

PDO и оператор LIMIT

Важно! Когда PDO работает в режиме эмуляции, все данные, которые были переданы напрямую в execute() , форматируются как строки. То есть, эскейпятся и обрамляются кавычками. Поэтому LIMIT ?,? превращается в LIMIT "10", "10" и очевидным образом вызывает ошибку синтаксиса и, соответственно, пустой массив данных.

Решение #1: Отключить режим эмуляции:

$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Решение #2: Биндить эти цифры через bindValue() , принудительно выставляя им тип PDO::PARAM_INT:

$limit = 3; $stm = $db->prepare("SELECT * FROM categories LIMIT ?"); $stm->bindValue(1, $limit, PDO::PARAM_INT); $stm->execute(); $data = $stm->fetchAll(); echo ""; print_r($data);

PDO и оператор IN

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

$arr = ; $in = str_repeat("?,", count($arr) - 1) . "?"; $sql = "SELECT * FROM categories WHERE `id` IN ($in)"; $stm = $db->prepare($sql); $stm->execute($arr); $data = $stm->fetchAll(); echo ""; print_r($data);

Добавление записей $name = "Новая категория"; $query = "INSERT INTO `categories` (`name`) VALUES (:name)"; $params = [ ":name" => $name ]; $stmt = $pdo->prepare($query); $stmt->execute($params); Изменение записей $id = 1; $name = "Изменённая запись"; $query = "UPDATE `categories` SET `name` = :name WHERE `id` = :id"; $params = [ ":id" => $id, ":name" => $name ]; $stmt = $pdo->prepare($query); $stmt->execute($params); Удаление записей $id = 1; $query = "DELETE FROM `categories` WHERE `id` = ?"; $params = [$id]; $stmt = $pdo->prepare($query); $stmt->execute($params); Использование транзакций try { // Начало транзакции $pdo->beginTransaction(); // ... code // Если в результате выполнения нашего кода всё прошло успешно, // то зафиксируем этот результат $pdo->commit(); } catch (Exception $e) { // Иначе, откатим транзакцию. $pdo->rollBack(); echo "Ошибка: " . $e->getMessage(); }

Важно! Транзакции в PDO работают только с таблицами InnoDB

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


Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности и правилами сайта, изложенными в пользовательском соглашении