Описание констант PDO::FETCH_*.

  1. Классика
  2. PDO::FETCH_LAZY
  3. Самое полезное
  4. Объектное ориентирование
  5. Разное
  6. Untested
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, причем этот объект наделён целым ворохом уникальных свойств:

Проведём пару экспериментов. Попробуем запросить относительно большой объём данных, и посмотрим, как меняется потребление памяти, и заодно проверим ещё пару утверждений:

$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 indexundef 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->setFetchModePDO::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

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