В прошлой заметке мы познакомились с основами PDO и простейшими запросами выборки данных из базы данных. В этой заметке мы научимся управлять режимами получения данных.
Все предопределенные константы здесь
PDO::FETCH_BOTH
Аналог mysql_fetch_array()
. Все данные возвращаются в дублированном виде, с текстовыми индексами и цифровыми. Этот режим включен в PDO по умолчанию.
$stm = $db->query('SELECT * FROM categories LIMIT 1')->fetch(PDO::FETCH_BOTH);
// Результат
Array
(
[id] => 1
[0] => 1
[name] => Ноутбуки и планшеты
[1] => Ноутбуки и планшеты
)
PDO::FETCH_NUM
Аналог mysql_fetch_row()
. Только цифровые индексы:
$stm = $db->query('SELECT * FROM categories LIMIT 1')->fetch(PDO::FETCH_NUM);
// Результат
Array
(
[0] => 1
[1] => Ноутбуки и планшеты
)
PDO::FETCH_ASSOC
Аналог mysql_fetch_assoc()
Только текстовые индексы.
$stm = $db->query('SELECT * FROM categories LIMIT 1')->fetch(PDO::FETCH_ASSOC);
// Результат
Array
(
[id] => 1
[name] => Ноутбуки и планшеты
)
PDO::FETCH_OBJ
Аналог mysql_fetch_object()
без указания имени класса, возвращает экземпляр stdClass
$stm = $db->query('SELECT * FROM categories LIMIT 1')->fetch(PDO::FETCH_OBJ);
// Результат
stdClass Object
(
[id] => 1
[name] => Ноутбуки и планшеты
)
PDO::FETCH_LAZY
В этом режиме не тратится лишняя память, и к тому же к колонкам можно обращаться любым из трех способов - через индекс, имя, или свойство (через ->
). Недостатком же данного режима является то, что он не работает с fetchAll()
$stm = $db->query('SELECT `name` FROM categories')->fetch(PDO::FETCH_LAZY);
// Результат
PDORow Object
(
[queryString] => SELECT `name` FROM categories
[name] => Ноутбуки и планшеты
)
PDO::FETCH_COLUMN
Когда необходимо получить только одну колонку из результата. Соответственно, имеет смысл только при использовании с fetchAll()
- и в этом случае возвращает сразу одномерный массив.
$stm = $db->query('SELECT `name` FROM categories')->fetchAll(PDO::FETCH_COLUMN);
// Результат
Array
(
[0] => Ноутбуки и планшеты
[1] => Компьютеры и периферия
[2] => Комплектующие для ПК
[3] => Смартфоны и смарт-часы
[4] => Телевизоры и медиа
[5] => Игры и приставки
[6] => Аудиотехника
[7] => Фото-видеоаппаратура
[8] => Офисная техника и мебель
[9] => Сетевое оборудование
[10] => Крупная бытовая техника
[11] => Товары для кухни
[12] => Красота и здоровье
[13] => Товары для дома
[14] => Инструменты
[15] => Автотовары
)
PDO::FETCH_KEY_PAIR
Малоизвестный, но очень полезный режим, когда из двух запрошенных полей содержимое первого становится ключом, а второго - значением одномерного массива.
$stm = $db->query('SELECT `name`, `id` FROM categories')->fetchAll(PDO::FETCH_KEY_PAIR);
// Результат
Array
(
[Ноутбуки и планшеты] => 1
[Компьютеры и периферия] => 2
[Комплектующие для ПК] => 3
[Смартфоны и смарт-часы] => 4
[Телевизоры и медиа] => 5
[Игры и приставки] => 6
[Аудиотехника] => 7
[Фото-видеоаппаратура] => 8
[Офисная техника и мебель] => 9
[Сетевое оборудование] => 10
[Крупная бытовая техника] => 11
[Товары для кухни] => 12
[Красота и здоровье] => 13
[Товары для дома] => 14
[Инструменты] => 15
[Автотовары] => 16
)
PDO::FETCH_UNIQUE
Похож на предыдущий, но в качестве значения возвращает всю оставшуюся строку. C fetch()
этот режим не возвращает ничего вразумительного, а вот с fetchAll()
как раз получается такой, весьма востребованный режим. Главное, чтобы первой колонкой в запросе выбиралось уникальное поле - тогда оно будет использовано в качестве индекса возвращаемого массива, вместо обычной нумерации
$stm = $db->query('SELECT * FROM categories LIMIT 3')->fetchAll(PDO::FETCH_UNIQUE);
// Результат
Array
(
[1] => Array
(
[name] => Ноутбуки и планшеты
[0] => Ноутбуки и планшеты
)
[2] => Array
(
[name] => Компьютеры и периферия
[0] => Компьютеры и периферия
)
[3] => Array
(
[name] => Комплектующие для ПК
[0] => Комплектующие для ПК
)
)
PDO::FETCH_GROUP
Группирует значения по первой колонке. К примеру, нижеследующий код разобьёт пользователей на мальчиков и девочек, и положит их в разные массивы:
$data = $pdo->query('SELECT sex, name, car FROM users')->fetchAll(PDO::FETCH_GROUP);
// Результат
array (
'male' => array ( 0 =>
array (
'name' => 'John',
'car' => 'Toyota',
),
1 => array (
'name' => 'Mike',
'car' => 'Ford',
),
),
'female' => array (
0 => array (
'name' => 'Mary',
'car' => 'Mazda',
),
1 => array (
'name' => 'Kathy',
'car' => 'Mazda',
),
),
)
То есть, этот режим идеально подходит для классической задачи вывести события сгруппированные по дням (или вывести товары, сгруппированные по категориям). Также может комбинироваться с PDO::FETCH_COLUMN
:
$sql = "SELECT sex, name FROM users";
$data = $pdo->query($sql)->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_COLUMN);
// Результат
array (
'male' =>
array (
0 => 'John',
1 => 'Mike',
),
'female' =>
array (
0 => 'Mary',
1 => 'Kathy',
),
)
PDO::FETCH_CLASS
Создаёт объект указанного класса, заполняя его свойства данными из БД. Однако здесь, увы, начинаются неудобства и непоследовательность в работе вызывающих функций. Если для fetchAll()
можно написать красиво и компактно
$data = $pdo->query('SELECT * FROM users LIMIT 1')->fetchAll(PDO::FETCH_CLASS, 'Foo');
то для fetch()
приходится писать такую колбасу:
$stmt = $pdo->query('SELECT * FROM users LIMIT 1');
$stmt->setFetchMode( PDO::FETCH_CLASS, 'Foo');
$data = $stmt->fetch();
Из-за того что fetch()
не позволяет передать имя класса, мы вынуждены пользоваться setFetchMode()
. А учитывая, что эта функция возвращает булево значение, а не ссылку на объект, мы не можем использовать method chaining. Также следует помнить, что в этом режиме PDO будет вызывать магический метод __set()
если свойство, совпадающее с именем поля, не найдено в объекте. Для PHP это означает, что если в объекте отсутствует такой метод, то все колонки строки, полученной из БД, будут назначены переменным класса. Если же мы хотим присвоить значения только существующим переменным, то этот момент надо контролировать с помощью метода __set()
. Например
class Foo
{
private $name;
public function __set($name, $value) {}
}
$data = $pdo->query('SELECT * FROM users LIMIT 1')
->fetchAll(PDO::FETCH_CLASS, 'Foo');
array(1) {
[0]=> object(Foo)#3 (1) {
["name":"Foo":private]=> string(4) "John"
}
}
в то время как у класса с пустым __set()
будут заполнены только существующие свойства:
class Foo {}
$data = $pdo->query('SELECT * FROM users LIMIT 1')
->fetchAll(PDO::FETCH_CLASS, 'Foo');
// Результат
array(1) {
[0]=> object(Foo)#3 (3) {
["name"] => string(4) "John"
["sex"] => string(4) "male"
["car"] => string(6) "Toyota"
}
}
Можно, кстати, заметить, что PDO присваивает значения и приватным свойствам, что несколько неожиданно, но очень удобно.
PDO::FETCH_CLASSTYPE
Очень интересная константа. Представляет собой не самостоятельный режим получения данных, а флаг-модификатор, изменяющий поведение других режимов. При её использовании PDO будет брать имя класса из первой колонки полученных из БД данных. То есть, с её помощью код для fetch()
можно сделать короче
class Foo {}
$data = $pdo->query("SELECT 'Foo', name FROM users LIMIT 1")
->fetch(PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE);
// Результат
object(Foo)#3 (1) {
["name"]=> string(4) "John"
}
PDO::FETCH_PROPS_LATE
Ещё один флаг-модификатор. По умолчанию PDO присваивает значения свойствам класса до вызова конструктора. При помощи же данной константы это поведение можно изменить - сначала будет вызываться конструктор:
class Foo {
private $name;
public function __construct() {
$this->name = NULL;
}
}
$data = $pdo->query('SELECT name FROM users LIMIT 1')
->fetchAll(PDO::FETCH_CLASS, 'Foo');
var_dump($data);
$data = $pdo->query('SELECT name FROM users LIMIT 1')
->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Foo');
var_dump($data);
// Результат
array(1) {
[0]=> object(Foo)#3 (1) {
["name":"Foo":private]=>
NULL
}
}
array(1) {
[0]=> object(Foo)#4 (1) {
["name":"Foo":private]=> string(4) "John"
}
}
PDO::FETCH_INTO
В отличие от PDO::FETCH_CLASS
не создаёт новый объект, а обновляет существующий. Соответственно, в качестве параметра передается переменная с объектом. По очевидным причинам имеет смысл только с fetch()
class Foo
{
public $name;
public $state;
public function __construct()
{
$this->name = NULL;
}
}
$foo = new Foo;
$foo->state = "up'n'running";
var_dump($foo);
$stmt = $pdo->query('SELECT name FROM users LIMIT 1');
$stmt->setFetchMode(PDO::FETCH_INTO, $foo);
$data = $stmt->fetch();
var_dump($data, $foo);
// Результат
object(Foo)#2 (2) {
["name"] => NULL
["state"] => string(12) "up'n'running"
}
object(Foo)#2 (2) {
["name"] => string(4) "John"
["state"] => string(12) "up'n'running"
}
object(Foo)#2 (2) {
["name"] => string(4) "John"
["state"] => string(12) "up'n'running"
}
Как видно, fetch()
возвращает тот же объект, что представляется мне несколько избыточным. Также, с сожалением приходится констатировать, что в отличие от PDO::FETCH_CLASS
, этот режим не присваивает значения приватным свойствам.
PDO::FETCH_SERIALIZE
Ещё один флаг для PDO::FETCH_CLASS
. Должен возвращать объект, который хранился в БД в сериализованном виде. Конструктор не вызывается. На данный момент не работает. Должно быть что-то вроде такого
class foo {}
$foo = new foo;
$foo->status="up'n'running";
$sFoo = serialize($foo);
// записываем $sFoo в БД
// и потом что-то вроде
$stmt = $pdo->query('SELECT sFoo FROM table');
$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE, 'foo');
$foo = $stmt->fetch();
PDO::FETCH_FUNC
Для любителей замыканий. Работает только внутри fetchAll()
. В параметры функции PDO передаёт переменные для каждого полученного поля, что может быть неудобным - нет доступа к именам полей, а только к значениям. К примеру, эмуляция работы PDO::FETCH_COLUMN
:
$data = $pdo
->query('SELECT name FROM users')
->fetchAll(PDO::FETCH_FUNC, function($first) {return $first;});
PDO::FETCH_NAMED
Почти то же самое, что PDO::FETCH_ASSOC
, но с одним отличием. Много раз я встречал на форумах вопросы о том, как получить значения полей с одинаковыми именами из разных таблиц при джойне. Всегда ответ был один - писать алиасы руками в запросе или использовать цифровые индексы. А вот и ответ от PDO: получение данных в этом режиме аналогично PDO::FETCH_ASSOC
, но если встречаются поля с одинаковыми именами, то все значения по очереди записываются во вложенный массив. Допустим, у нас есть таблицы users и companies, причем в обеих есть поле name. Если получать данные традиционным путём, то одно из полей будет съедено:
$data = $pdo->query("SELECT * FROM users, companies WHERE users.name=username")->fetch();
// Результат
array(3) {
["name"] => string(10) "ACME, Inc."
["sex"] => string(4) "male"
["username"] => string(4) "John"
}
Если же указать это флаг, то все значения колонок с совпадающими именами будут собраны во вложенном массиве в порядке поступления:
$data = $pdo->query("SELECT * FROM users, companies WHERE users.name=username")
->fetch(PDO::FETCH_NAMED);
// Результат
array(3) {
["name"]=> array(2) {
[0]=> string(4) "John"
[1]=> string(10) "ACME, Inc."
}
["sex"] => string(4) "male"
["username"] => string(4) "John"
}
PDO::FETCH_BOUND
Очень интересный режим, сильно отличающийся от других. В отличие от всех остальных, он не возвращает не массив или объект. Вместо этого он присваивает значения переменным, предварительно указанным с помощью bindColumn()
- режим, аналогичный тому, что используется в mysqli при получении данных из подготовленного запроса. Пример есть в документации
Материал подготовлен на основе официальной статьи .