Константи методу fetch з PDO
Хоча цікаві для нас константи частково описуються на сторінках документації, присвячених методам fetch()та fetchAll(), повний список наведено лише на сторінці із загальним списком констант PDO, що не здається мені дуже зручним, і може бути тією причиною, з якої деякі особливо цікаві режими отримання даних опинилися поза увагою більшості розробників. Найбільш затребуванні константи методу fetch із загального списку і робиті на кілька категорій.
2.Найкорисніше
3.Об'єктне орієнтування
4.Різне
Класика
Для початку перерахуємо константи, які дублюють стандартну поведінку теплих лампових функцій mysql. Всі приклади надаються з використанням функції var_export().
PDO::FETCH_BOTH
аналог mysql_fetch_array(). Усі дані повертаються у дубльованому вигляді - і з текстовими індексами, і з цифровими. Цей режим включено до стандартного PDO.TCH_NUM
знов старий знайомий, аналог mysql_fetch_row(). Лише цифрові індекси:
<?$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_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, додаючи до рядка мегабайтне поле. Після цього ми отримуємо рядок через цю константу – обсяг пам'яті не змінився. Тільки після того, як ми привласнюємо значення мегабайтного поля змінної – він збільшується. Для контролю виводимо ім'я з таблиці користувачів. Потім отримуємо ще один рядок, вже в одному із стандартних режимів. Бачимо, що обсяг пам'яті відразу підскакує, на відміну від попереднього випадку. Економія в першому випадку очевидна! Також ми бачимо відсутність нотису та зміну стану
$lazyпісля викликуfetch().З усієї цієї інформації можна зробити висновок, що об'єкт PDORow - це канал прямого зв'язку з
астраломresultSet-ом драйвера БД. І, не маючи власного стану, просто читає дані з поточної позиції курсору. Враховуючи все вищесказане, можна тільки дивуватися, чому ця константа досі так рідко використовується.Найкорисніше
Тут я вирішив зібрати найкорисніші, на мій погляд, режими, які повертають дані у найбільш затребуваних у повсякденній розробці форматах.
Примітка: тут і далі всі приклади даються при увімкненому за замовчуванням форматі виводу
PDO::FETCH_ASSOCPDO::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(). А враховуючи, що ця функція повертає булеве значення, а не посилання на об'єкт, ми не можемо використовувати метод 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-ом покопалися, і в результаті він накотив баг-репорт, але віз і нині там.
Різне
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 функцій з управління курсором при отриманні даних із БД. Приклад є в документації