Как настроить и использовать PDO для доступа к базе данных в Linux
Задача
Узнайте, как настроить и использовать PDO для доступа к базе данных: от режимов ошибок до методов выборки.Требования
- Стандартное знание MySQL и
mysql
клиента командной строки; - Быть знакомым с фундаментальными концепциями объектно-ориентированного программирования
- PHP> = 5.1
- Есть рабочая база данных MySQL / MariaDB
трудность
СРЕДНИЙУсловные обозначения
- # - требует, чтобы данные команды linux выполнялись с правами root либо напрямую как пользователь root, либо с помощью
sudo
команды - $ - требует, чтобы данные команды linux выполнялись как обычный непривилегированный пользователь

Введение
PDO является аббревиатурой отPHP Data Objects
: это расширение PHP для взаимодействия с базами данных посредством использования объектов. Одна из его сильных сторон заключается в том, что она не связана строго с какой-то конкретной базой данных: ее интерфейс обеспечивает общий способ доступа к нескольким различным средам, в том числе:- MySQL
- SQLite
- PostgreSQL
- Microsoft SQL Server
Создать тестовую базу данных и таблицу
Первое, что мы собираемся сделать, это создать базу данных для этого урока: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: имя источника данных
Теперь, когда у нас есть база данных, мы должны определитьDSN
. DSN обозначает
Data Source Name
, и это в основном набор информации, необходимой для подключения к базе данных, представленной в виде строки. Синтаксис может отличаться в зависимости от базы данных, к которой вы хотите подключиться, но, поскольку мы взаимодействуем с 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 сгенерирует a
PDOException
, что нарушит поток сценариев, и это особенно полезно в случае PDO transactions
(мы увидим, какие транзакции будут позже в этом уроке).
PDO :: ERRMODE_WARNING
С помощью этой опции PDO установит код ошибки и информацию как проиндексированные
PDO::ERRMODE_SILENT
, но также выведет a
WARNING
, что не нарушит поток скрипта.
Установка режима выборки по умолчанию
Другой важный параметр может быть указан через PDO :: DEFAULT_FETCH_MODE. постоянная. Это позволяет вам указать метод выборки по умолчанию, который будет использоваться при получении результатов запроса. Это наиболее часто используемые параметры:PDO :: FETCH_BOTH:
Это по умолчанию. При этом результат, полученный запросом выборки, будет проиндексирован как по целому числу, так и по имени столбца. Применение этого режима извлечения при извлечении строки из таблицы планет даст нам такой результат:
$stmt = $pdo->query("SELECT * FROM planets");
$results = $stmt->fetch(PDO::FETCH_BOTH);
Array ( [id] => 1 [0] => 1 [name] => earth [1] => earth [color] => blue [2] => blue )
PDO :: FETCH_ASSOC:
С этой опцией результат будет сохранен в переменной, associative
array
в которой каждый ключ будет именем столбца, а каждое значение будет соответствующим значением в строке:
$stmt = $pdo->query("SELECT * FROM planets");
$results = $stmt->fetch(PDO::FETCH_ASSOC);
Array ( [id] => 1 [name] => earth [color] => blue )
PDO :: FETCH_NUM
Этот режим извлечения возвращает извлеченную строку в
0-indexed array:
Array ( [0] => 1 [1] => earth [2] => blue )
PDO :: FETCH_COLUMN
Этот метод извлечения полезен при извлечении только значений столбца и возвращает все результаты в простом одномерном массиве. Например этот запрос:
$stmt = $pdo->query("SELECT name FROM planets");
Вернул бы этот результат:Array ( [0] => earth [1] => mars [2] => jupiter )
PDO :: FETCH_KEY_PAIR
Этот метод извлечения полезен при получении значений только 2 столбцов. Он вернет результаты в виде ассоциативного массива, в котором значения, извлеченные из базы данных для первого указанного столбца в запросе, будут использоваться в качестве ключей массива, в то время как значения, извлеченные для второго столбца, будут представлять ассоциативный значения массива:
$stmt = $pdo->query("SELECT name, color FROM planets");
$result = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
Вернется:Array ( [earth] => blue [mars] => red [jupiter] => strange )
PDO :: FETCH_OBJECT:
При использовании PDO::FETCH_OBJECT
константы anonymous object
будет создаваться an
для каждой извлеченной строки. Его (публичные) свойства будут названы в честь столбцов, а результаты запроса будут использованы в качестве их значений. Применение этого режима извлечения к тому же запросу, приведенному выше, вернет нам результат в виде:
$results = $stmt->fetch(PDO::FETCH_OBJ);
stdClass Object ( [name] => earth [color] => 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
вами необходимо использовать setFechMode()
метод объекта оператора перед тем, как пытаться получить данные, например:$stmt = $pdo->query("SELECT name, color FROM planets");
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Planet');
Мы предоставили константу опции fetch PDO::FETCH_CLASS
в качестве первого аргумента метода setFetchMode () и имя класса, который следует использовать для создания объекта (в данном случае «Planet»), в качестве второго. Теперь мы бежим:$planet = $stmt->fetch();
Объект Планета должен был быть создан:var_dump($planet);
Planet Object ( [name:Planet:private] => earth [color:Planet:private] => blue )Обратите внимание, как значения, полученные в результате запроса, были присвоены соответствующим свойствам объекта, даже если они являются частными.
Присвоение свойств после строительства объекта
Класс планеты не имеет явного конструктора, поэтому нет проблем при назначении свойств; но что, если у класса был конструктор, в котором свойство было назначено или изменено? Поскольку значения присваиваются до вызова конструктора, они были бы перезаписаны.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
а второй -
color
. Эти аргументы имеют значение по умолчанию соответственно
moon
и gray
: это означает, что если значения не указаны явно, это будут назначенные значения по умолчанию.В этом случае, если мы не используем
FETCH_PROPS_LATE
, независимо от значений, извлеченных из базы данных, свойства всегда будут иметь значения по умолчанию, потому что они будут перезаписаны при создании объекта. Давайте проверим это. Сначала запустим запрос:$stmt = $pdo->query("SELECT name, color FROM solar_system WHERE name = 'earth'");
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Planet');
$planet = $stmt->fetch();
Затем мы сбрасываем 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:while ($planet = $stmt->fetch()) {
// do stuff with the results
}
или получая все результаты сразу. В этом случае, как сказано выше, при использовании
fetchAll()
метода вам не нужно указывать режим извлечения перед вызовом самого метода, но в тот момент, когда вы вызываете его:$stmt->fetchAll(PDO::FETCH_CLASS|PDO_FETCH_PROPS_LATE, 'Planet', ['moon', 'gray']);
PDO :: FETCH_INTO
С этим установленным методом выборки PDO не будет создавать новый объект, вместо этого он обновит свойства существующего, но только если они есть public
, или если вы используете __set
магический метод внутри объекта.
Подготовлено против прямых заявлений
У PDO есть два способа выполнения запросов: один - прямой, одношаговый. Другой, более безопасный способ использованияprepared statements
.Прямые запросы
При использовании прямых запросов у вас есть два основных метода:query()
и
exec()
. Прежний возврат возвращает PDOStatemnt
объект, который вы можете использовать для доступа к результатам с помощью методов fetch()
или
fetchAll()
: вы используете его для оператора, который не изменяет таблицу, например SELECT
.Последний, вместо этого, возвращает номер строки, которая была изменена запросом: мы используем его для операторов, которые изменяют строки, например
INSERT
, DELETE
или
UPDATE
. Прямые операторы должны использоваться только тогда, когда в запросе нет переменных, и вы абсолютно уверены, что это безопасно и правильно экранировано.Подготовленные заявления
PDO также поддерживает двухэтапные подготовленные операторы: это полезно при использовании переменных в запросе и в целом более безопасно, посколькуprepare()
метод будет выполнять все необходимые экранирования для нас. Давайте посмотрим, как используются переменные. Представьте, что мы хотим вставить свойства объекта Planet в Planets
таблицу. Сначала мы подготовим запрос:$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(?, ?)");
Как было сказано ранее, сначала мы будем использовать prepare()
метод, который принимает SQL-запрос в качестве аргумента, используя заполнители для переменных. Теперь заполнители могут быть двух типов:Позиционные заполнители
При использовании ?
позиционных заполнителей мы можем получить более сжатый код, но мы должны предоставить значения, которые будут заменены в том же порядке имен столбцов, в массиве, предоставленном в качестве аргумента execute()
метода:
$stmt->execute([$planet->name, $planet->color]);
Именованные заполнители
Используя named placeholders
, нам не нужно соблюдать определенный порядок, но мы собираемся создать более подробный код. При выполнении execute()
метода мы должны предоставить значения в форме символа, associative array
в котором каждый ключ будет именем используемого заполнителя, а соответствующее значение будет тем, которое будет подставлено в запрос. Например, приведенный выше запрос будет выглядеть так:
$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)");
$stmt->execute(['name' => $planet->name, 'color' => $planet->color]);
Методы подготовки и выполнения могут использоваться как при выполнении запросов, которые изменяют или просто извлекают данные из базы данных. В первом случае мы используем методы извлечения, которые мы видели выше, для извлечения данных, в то время как во втором мы можем извлечь количество затронутых строк, используя rowCount()
метод.Методы bindValue () и bindParam ()
Для того, чтобы обеспечить значения подставляются в запросе можно также использоватьbindValue()
и bindParam()
методы. Первый связывает значение переменной, предоставленной с соответствующим позиционным или именованным заполнителем, используемым при подготовке запроса. Используя приведенный выше пример, мы бы сделали:$stmt->bindValue('name', $planet->name, PDO::PARAM_STR);
Мы связываем значение $planet->name
с :name
заполнителем. Обратите внимание, что используя методы bindValue () и bindParam (), мы можем указать в качестве третьего аргумента type
переменную, используя в этом случае связанную константу 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 отключает автокоммит запросов, затем внутри блока try-catch запросы выполняются в требуемом порядке. На этом этапе, если значение не PDOException
задано, запросы фиксируются с помощью commit()
метода, в противном случае с помощью rollBack()
метода транзакции возвращаются и автокоммит восстанавливается.Таким образом, при выдаче нескольких запросов всегда будет согласованность. Совершенно очевидно, что вы можете использовать транзакции PDO, только когда для
PDO::ATTR_ERRMODE
них установлено значение PDO::ERRMODE_EXCEPTION
.