Описание констант 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 функций по управлению курсором при получении данных из БД. Пример есть в документации
Написать комментарий
Пожалуйста, воздержитесь от посылки спама.
Сообщения, содержащие гиперссылки, проходят премодерацию.