Описание констант PDO::FETCH_*.
Comments (1)Как уже говорилось в статье Как работать с PDO?, константы, задающие режим получения данных (наряду с передачей параметров напрямую в execute()
) делают PDO по-настоящему удобной библиотекой для работы с БД, а не просто еще одним (пусть и универсальным) API для доступа к базе данных. С помощью этих констант можно значительно сократить код многих рутинных операций, поскольку они позволяют получить данные сразу в нужном формате.
Хотя интересующие нас константы частично описываются на страницах документации, посвященных методам fetch()
и fetchAll()
, полный список приведен только на странице с общим списком констант PDO, что не кажется мне очень удобным, и может являться той причиной, по которой некоторые особенно интересные режимы получения данных оказались вне поля зрения большинства разработчиков. Я решил выделить интересующие нас константы из общего списка и разбить для удобства на несколько категорий, поскольку их количество впечатляет - ни много ни мало, а целых 25 штук!
Классика
Для начала перечислим константы, которые дублируют стандартное поведение теплых ламповых функций mysql. Все примеры даются с использованием функции var_export()
.
PDO::FETCH_BOTH
аналог mysql_fetch_array()
. Все данные возвращаются в дублированном виде - и с текстовыми индексами, и с цифровыми. Этот режим включен в PDO по умолчанию.
<?$user = $pdo->query("SELECT * from users LIMIT 1")->fetch(PDO::FETCH_BOTH);
/*
array (
'id' => '104',
0 => '104',
'name' => 'John',
1 => 'John',
'sex' => 'male',
2 => 'male',
'car' => 'Toyota',
3 => 'Toyota',
)*/?>
PDO::FETCH_NUM
снова старый знакомый, аналог mysql_fetch_row()
. Только цифровые индексы:
<?$user = $pdo->query("SELECT * from users LIMIT 1")->fetch(PDO::FETCH_NUM);
/*
array (
0 => '104',
1 => 'John',
2 => 'male',
3 => 'Toyota',
)*/?>
PDO::FETCH_ASSOC
то же самое, аналог mysql_fetch_assoc()
, только текстовые индексы.
<?$user = $pdo->query("SELECT * from users LIMIT 1")->fetch(PDO::FETCH_ASSOC);
/*
array (
'id' => '104',
'name' => 'John',
'sex' => 'male',
'car' => 'Toyota',
)*/?>
См. также PDO::FETCH_NAMED
PDO::FETCH_OBJ
аналог mysql_fetch_object()
без указания имени класса, возвращает экземпляр stdClass
<?$user = $pdo->query("SELECT * from users LIMIT 1")->fetch(PDO::FETCH_OBJ);
/*
stdClass::__set_state(array(
'id' => '104',
'name' => 'John',
'sex' => 'male',
'car' => 'Toyota',
))*/?>
PDO::FETCH_LAZY
Эта константа настолько примечательна и ни на что не похожа, что я решил вынести её в отдельный раздел. Начнем с того, что возвращаемая с её помощью переменная является экземпляром особенного класса PDORow
, причем этот объект наделён целым ворохом уникальных свойств:
- Во-первых, эта переменная не содержит сами запрошенные данные, а отдаёт их по запросу (на что намекает название)
- Во-вторых, помимо запрошенных данных, в объекте присутствует загадочная переменная, которая называется queryString и содержит SQL запрос(!) (что намекает о родственности этого класса PDOStatement):
<?$lazy = $pdo->query("SELECT name FROM users")->fetch(PDO::FETCH_LAZY);
/*
object(PDORow)#3 (2) {
["queryString"] => string(22) "SELECT name FROM users"
["name"] => string(4) "John"
}*/?>Для пытливых - да, перезаписать queryString можно :)
- В-третьих, эту переменную невозможно сохранить в сессии (или, другими словами - сериализовать)
- В-четвертых, получать из неё данные можно как угодно, любым из трёх способов: через числовой индекс, ассоциативный, или обращаясь к свойству класса через ->
- В-пятых, обращение к несуществующим свойствам не вызывает нотис Undefined property / index. Молча возвращается
NULL
. - В-шестых, эта переменная меняет своё состояние от последующих вызовов
fetch()
с другими константами. Плюс ко всему, эта константа не работает сfetchAll()
, а только сfetch()
.
Проведём пару экспериментов. Попробуем запросить относительно большой объём данных, и посмотрим, как меняется потребление памяти, и заодно проверим ещё пару утверждений:
<?$stmt = $pdo->query("SELECT *, REPEAT(' ', 1024 * 1024) big FROM users");
echo 'start ', round(memory_get_usage() / 1024), PHP_EOL;
$lazy = $stmt->fetch(PDO::FETCH_LAZY);
echo 'lazy fetch ', round(memory_get_usage() / 1024), PHP_EOL;
$big = $lazy[3];
echo 'lazy assign ', round(memory_get_usage() / 1024), PHP_EOL;
echo 'lazy name ', $lazy[0], PHP_EOL;
echo 'lazy undef ', var_dump($lazy['undef']);
echo '------------', PHP_EOL;
$num = $stmt->fetch(PDO::FETCH_NUM);
echo 'num fetch ', round(memory_get_usage() / 1024), PHP_EOL;
$big2 = $num[3];
echo 'num assign ', round(memory_get_usage() / 1024), PHP_EOL;
$big2 .= ''; // чтобы вызвать copy-on-write
echo 'num assign2 ', round(memory_get_usage() / 1024), PHP_EOL;
echo 'lazy name ', $lazy[0], PHP_EOL;
echo 'num undef ', var_dump($num['undef']);?>
Вывод:
<?start 228
lazy fetch 228
lazy assign 1252
lazy name John
lazy undef NULL
------------
num fetch 2277
num assign 2277
num assign2 3301
lazy name Mike
num undef
Notice: Undefined index: undef in pdo.php on line 48
NULL?>
Как видно, этот код опрашивает нашу таблицу users, добавляя к строке мегабайтное поле. После этого мы получаем строку через эту константу - объём памяти не поменялся. Только после того, как мы присваиваем значение мегабайтного поля переменной - он увеличивается. Для контроля выводим имя из таблицы users.
Затем получаем ещё одну строку, уже в одном из стандартных режимов. Видим, что объем памяти тут же подскакивает, в отличие от предыдущего случая. Экономия в первом случае налицо! Так же мы видим отсутствие нотиса и смену состояния $lazy
после вызова fetch()
.
Из всей этой информации можно сделать вывод, что объект PDORow - это канал прямой связи с астралом resultSet-ом драйвера БД. И, не имея собственного состояния, попросту читает данные с текущей позиции курсора. Учитывая всё вышесказанное можно только удивляться, почему эта константа до сих пор так редко используется.
Самое полезное
Здесь я решил собрать самые полезные, на мой взгляд, режимы, которые возвращают данные в наиболее востребованных в повседневной разработке форматах.
Примечание: здесь и далее все примеры даются при включенном по умолчанию формате вывода PDO::FETCH_ASSOC
PDO::FETCH_COLUMN
Вытаскивает только одну колонку из результата. Соответственно, имеет смысл только при использовании с fetchAll()
- и в этом случае возвращает сразу одномерный массив. Очень удобно.
<?$data = $pdo->query('SELECT name FROM users')->fetchAll(PDO::FETCH_COLUMN);
/* array (
0 => 'John',
1 => 'Mike',
2 => 'Mary',
3 => 'Kathy',
) */?>
Дополнительным параметром можно указать номер выбираемой колонки. Если этот флаг используется сам по себе, то номер указывать нет смысла (надо сразу в запросе выбирать только одну колонку, которая и будет выведена), но если в комбинации с другими, то номер может оказаться полезным.
PDO::FETCH_KEY_PAIR
Малоизвестный, но довольно полезный режим, когда из двух запрошенных полей содержимое первого становится ключом, а второго - значением одномерного массива.
<?$data = $pdo->query('SELECT name, car FROM users')->fetchAll(PDO::FETCH_KEY_PAIR);
/* array (
'John' => 'Toyota',
'Mike' => 'Ford',
'Mary' => 'Mazda',
'Kathy' => 'Mazda',
)?>
Требователен к количеству колонок в запросе - их должно быть строго две
PDO::FETCH_UNIQUE
похож на предыдущий, но в качестве значения возвращает всю оставшуюся строку. C fetch()
этот режим не возвращает ничего вразумительного, а вот с fetchAll()
как раз получается такой, весьма востребованный режим. Главное, чтобы первой колонкой в запросе выбиралось уникальное поле - тогда оно будет использовано в качестве индекса возвращаемого массива, вместо обычной нумерации
<?$data = $pdo->query('SELECT * FROM users')->fetchAll(PDO::FETCH_UNIQUE);
/* array (
'John' => array (
'sex' => 'male',
'car' => 'Toyota',
),
'Mike' => array (
'sex' => 'male',
'car' => 'Ford',
),
'Mary' => array (
'sex' => 'female',
'car' => 'Mazda',
),
'Kathy' => array (
'sex' => 'female',
'car' => 'Mazda',
),
) */?>
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',
),
)*/?>
Объектное ориентирование
Разумеется, простым stdObject
возможности (я бы даже сказал - аппетиты) PDO по работе с объектами не исчерпываются. Дальше идет целая серия режимов для всевозможной манипуляции ими.
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_INTO, но объект передается в сериализованном массиве". А куда передавать-то? В параметры что ли? пробовал и так, и сяк - ничего не получалось. Только когда нашел юнит-тест, посвященный этому режиму, стало понятно, что объект должен придти из БД. Но всё равно не получается - пишет, "cannot unserialize class".
В общем, предполагается, что этот режим должен создавать объект из сериализованного представления, хранящегося в БД. Но это не работает, поскольку при использовании этого метода из базы возвращается не то, что туда клали! Вместо исходного объекта возвращается внонимный класс, который содержит два свойства - одно с именем объекта, и второе с исходным объектом. В общем, мы с zerkms-ом покопались, и в итоге он накатал баг-репорт, https://bugs.php.net/bug.php?id=68802 но воз и ныне там.
Разное
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 при получении данных из подготовленного запроса. Пример есть в документации
Untested
За неимением подходящей БД я не смог проверить работу еще нескольких констант. Но, на мой взгляд, их поведение достаточно очевидно и не нуждается в особом описании.
- PDO::ATTR_FETCH_CATALOG_NAMES - возвращает имена колонок в формате "каталог.имя_колонки". Поддерживается не всеми драйверами.
- PDO::ATTR_FETCH_TABLE_NAMES - возвращает имена колонок в формате "имятаблицы.имяколонки". Поддерживается не всеми драйверами.
- PDO::FETCH_ORI_* - 6 функций по управлению курсором при получении данных из БД. Пример есть в документации
Написать комментарий
Пожалуйста, воздержитесь от посылки спама.
Сообщения, содержащие гиперссылки, проходят премодерацию.