😎 » PHP - FAQ » PHP PDO MySQL » Константи методу fetch з PDO
84 0  

Константи методу fetch з PDO

Хоча цікаві для нас константи частково описуються на сторінках документації, присвячених методам fetch()та fetchAll(), повний список наведено лише на сторінці із загальним списком констант PDO, що не здається мені дуже зручним, і може бути тією причиною, з якої деякі особливо цікаві режими отримання даних опинилися поза увагою більшості розробників. Найбільш затребуванні константи методу fetch із загального списку і робиті на кілька категорій.

  1. Класика

2.Найкорисніше

3.Об'єктне орієнтування

4.Різне

Untested

Класика

Для початку перерахуємо константи, які дублюють стандартну поведінку теплих лампових функцій 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_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(). А враховуючи, що ця функція повертає булеве значення, а не посилання на об'єкт, ми не можемо використовувати метод 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 функцій з управління курсором при отриманні даних із БД. Приклад є в документації



Залишити свій коментар:

Досвід у веброзробці:

2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2009
2023