В прошлой заметке мы познакомились с основами 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 при получении данных из подготовленного запроса. Пример есть в документации

Материал подготовлен на основе официальной статьи .