Налаштування і використання PDO – розширення PHP Data Objects для роботи з базами даних


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

PDO (PHP Data Objects) – розширення PHP, яке реалізує взаємодію з базами даних за допомогою об’єктів. Профіт в тому, що відсутня прив’язка до конкретної системи управління базами даних.

Інтерфейс, що надається, підтримує, серед інших, такі популярні СУБД:

  • MySQL;
  • SQLite;
  • PostgreSQL;
  • Microsoft SQL Server.

У цьому керівництві представлений огляд PDO:

  1. Покроковий опис роботи з базами даних, починаючи з установки з’єднання до виконання вибірки.
  2. Порядок використання підготовлених запитів.
  3. Налаштування обробки помилок.

Для роботи знадобляться:

  • базові знання MySQL і досвід використання команди mysql у консолі;
  • розуміння основ об’єктно-орієнтованого програмування;
  • PHP >= 5.1;
  • робоча СУБД MySQL/MariaDB.

Створення тестової бази даних і таблиці

Спершу створимо базу даних для цього керівництва:

CREATE DATABASE solar_system;GRANT ALL PRIVILEGES ON solar_system.* TO 'testuser'@'localhost'IDENTIFIED BY 'testpassword';

Користувачеві з логіном testuser і паролем testpassword надали повні права доступу до бази solar_system.

Тепер створимо таблицю і заповнимо даними, астрономічна точність яких не мається на увазі :

USE solar_system;CREATE TABLE planets ( id TINYINT (1) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (id), name VARCHAR (10) NOT NULL, color VARCHAR (10) NOT NULL );INSERT INTO planets (name, color) VALUES ('earth', 'blue')('mars', 'red')('jupiter', 'strange');

Опис з’єднання

Тепер, коли створена база, визначимо DSN (Data Source Name) – відомості для підключення до бази, представлені у вигляді рядка. Синтаксис опису відрізняється залежно від використовуваної СУБД. У прикладі працюємо з MySQL/MariaDB, тому вказуємо:

  • тип драйвера;
  • ім’я хоста, де розташована СУБД;
  • порт (необов’язково, якщо використовується стандартний порт 3306);
  • ім’я бази даних;
  • кодування (необов’язково).

Рядок DSN в цьому випадку виглядає таким чином:

$dsn = "mysql:host=localhost;port=3306;dbname=solar_system;charset=utf8";

Першим вказується database prefix. У прикладі – mysql. Префікс відділяється від іншої частини рядка двокрапкою, а кожен наступний параметр – крапкою з комою.

Створення PDO- об’єкту

Тепер, коли рядок DSN готовий, створимо PDO- об’єкт. Конструктор на вході приймає наступні параметри:

  1. Рядок DSN.
  2. Ім’я користувача, що має доступ до бази даних.
  3. Пароль цього користувача.
  4. Масив з додатковими параметрами (необов’язково).
$options =[ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ];$pdo = new PDO ($dsn, 'testuser', 'testpassword', $options);

Додаткові параметри можна також визначити після створення об’єкту за допомогою методу SetAttribute:

$pdo ->SetAttribute ( - (-000-0-) -, PDO::ERRMODE_EXCEPTION);

Визначення методу вибірки за умовчанням

PDO::DEFAULT_FETCH_MODE – важливий параметр, який визначає метод вибірки за умовчанням. Вказаний метод використовується при отриманні результату виконання запиту.

PDO::FETCH_BOTH

Режим за умовчанням. Результат вибірки індексується як номерами (починаючи з 0), так і іменами стовпців :

$stmt = $pdo ->query ("SELECT * FROM planets");$results = $stmt ->fetch (PDO::FETCH_BOTH);

Після виконання запиту з цим режимом до тестової таблиці планет отримаємо наступний результат:

Array ( [id] => 1 [0] => 1 [name] => earth [1] => earth [color] => blue [2] => blue)

PDO::FETCH_ASSOC

Результат зберігається в асоціативному масиві, в якому ключ – ім’я стовпця, а значення, – відповідне значення рядка :

$stmt = $pdo ->query ("SELECT * FROM planets");$results = $stmt ->fetch (PDO::FETCH_ASSOC);

В результаті отримаємо:

Array ( [id] => 1 [name] => earth [color] => blue)

PDO::FETCH_NUM

При використанні цього режиму результат представляється у вигляді масиву, індексованого номерами стовпців (починаючи з 0) :

Array ( [0] => 1 [1] => earth [2] => blue)

PDO::FETCH_COLUMN

Цей варіант корисний, якщо треба отримати перелік значень одного поля у вигляді одновимірного масиву, нумерація якого починається з 0. Наприклад:

$stmt = $pdo ->query ("SELECT name FROM planets");

В результаті отримаємо:

Array ( [0] => earth [1] => mars [2] => jupiter)

PDO::FETCH_KEY_PAIR

Використовуємо цей варіант, якщо треба отримати перелік значень двох полів у вигляді асоціативного масиву. Ключі масиву – це дані першого стовпця вибірки, значення масиву – дані другого стовпця. Наприклад:

$stmt = $pdo ->query ("SELECT name, color FROM planets");$result = $stmt ->fetchAll (PDO::FETCH_KEY_PAIR);

В результаті отримаємо:

Array ([earth] => blue [mars] => red [jupiter] => strange)

PDO::FETCH_OBJECT

При використанні PDO::FETCH_OBJECT для кожного витягнутого рядка створюється анонімний об’єкт. Його загальнодоступні (public) властивості – імена стовпців вибірки, а результати запиту використовуються як їх значення:

$stmt = $pdo ->query ("SELECT name, color FROM planets");$results = $stmt ->fetch (PDO::FETCH_OBJ);

В результаті отримаємо:

stdClass Object ([name] => earth [color] => blue)

PDO::FETCH_CLASS

В цьому випадку, як і в попередньому, значення стовпців стають властивостями об’єкту. Проте вимагається вказати існуючий клас, який використовуватиметься для створення об’єкту. Розглянемо це на прикладі. Спершу створимо клас:

class Planet{ private $name; private $color; public function setName ($planet_name) { $this ->name = $planet_name; } public function setColor ($planet_color) { $this ->color = $planet_color; } public function getName (){ return $this ->name; } public function getColor (){ return $this ->color; }} 

Зверніть увагу, що у класу Planet закриті (private) властивості і немає конструктора. Тепер виконаємо запит.

Якщо використовується метод fetch з PDO::FETCH_CLASS, перед відправкою запиту на отримання даних треба застосувати метод setFetchMode:

$stmt = $pdo ->query ("SELECT name, color FROM planets");$stmt ->setFetchMode ( - (-000-0-) -, 'Planet');

Перший параметр, який передаємо методу setFetchMode, – константа PDO::FETCH_CLASS. Другий параметр – ім’я класу, який використовуватиметься при створенні об’єкту. Тепер виконаємо:

$planet = $stmt ->fetch ();var_dump ($planet);

В результаті отримаємо об’єкт Planet:

Planet Object ( [name:Planet:private] => earth [color:Planet:private] => blue)

Значення, отримані в результаті запиту, призначені відповідним властивостям об’єкту, навіть закритим.

Визначення властивостей після виконання конструктора

У класі Planet немає явного конструктора, тому проблем при призначенні властивостей не буде. За наявності у класу конструктора, в якому властивість була призначена або змінена, вони будуть перезаписані.

При використанні константи FETCH_PROPS_LATE значення властивостей привласнюватимуться після виконання конструктора :

class Planet{ private $name; private $color; public function __construct ($name = moon, $color = grey) { $this ->name = $name; $this ->color = $color; } public function setName ($planet_name) { $this ->name = $planet_name; } public function setColor ($planet_color) {
$this ->color = $planet_color; } public function getName (){ return $this ->name; } public function getColor (){ return $this ->color; }}

Ми змінили клас Planet, додавши конструктор, який приймає на вході два аргументи: name (ім’я) і color (колір). Значення цих полів за умовчанням: moon (місяць) і gray (сірий) відповідно.

Якщо не використати FETCH_PROPS_LATE, при створенні об’єкту властивості будуть перезаписані значеннями за умовчанням. Перевіримо це. Спочатку виконаємо запит:

$stmt = $pdo ->query ("SELECT name, color FROM solar_system WHERE name = 'earth'");$stmt ->setFetchMode (PDO::FETCH_CLASS, 'Planet');$planet = $stmt ->fetch ();var_dump ($planet);

В результаті отримаємо:

object (Planet) #2 (2) { ["name":" Planet": private]=> string (4)  " moon" ["color":" Planet": private]=> string (4)  " gray"}

Як і очікувалося, витягнуті з бази даних значення перезаписані. Тепер розглянемо рішення задачі з допомогою FETCH_PROPS_LATE (запит аналогічний):

$stmt ->setFetchMode (PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Planet');$planet = $stmt ->fetch ();var_dump ($planet);

В результаті отримаємо те, що треба :

object (Planet) #4 (2) {["name":" Planet": private]=>string (5)  " earth"["color":" Planet": private]=>string (4)  " blue"}

Якщо у конструктора класу немає значень за умовчанням, а вони потрібні, параметри конструктора задаються при виклику методу setFetchMode третім аргументом у вигляді масиву. Наприклад:

class Planet{ private $name; private $color; public function __construct ($name, $color) { $this ->name = $name; $this ->color = $color; } [...]}

Аргументи конструктора обов’язкові, тому виконаємо:

$stmt ->setFetchMode (PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Planet',['moon', 'gray']);

Параметри, що входять, виступають також в ролі значень за умовчанням, які потрібні для ініціалізації. Надалі вони будуть перезаписані значеннями з бази даних.

Отримання декількох об’єктів

Множинні результати витягаються у вигляді об’єктів за допомогою методу fetch усередині циклу while:

while ($planet = $stmt ->fetch()) { // обробка результатів}

Чи шляхом вибірки усіх результатів відразу. У другому випадку використовується метод fetchAll, причому режим вказується у момент виклику:

$stmt ->fetchAll (PDO::FETCH_CLASS|PDO_FETCH_PROPS_LATE, 'Planet',['moon', 'gray']);

PDO::FETCH_INTO

При виборі цього варіанту вибірки PDO не створює новий об’єкт, а оновлює властивості існуючого. Проте це можливо тільки для загальнодоступних (public) властивостей або при використанні в об’єкті “магічного” методу __set.

Підготовлені і прямі запити

У PDO два способи виконання запитів :

  • прямій, який складається з одного кроку;
  • підготовлений, який складається з двох кроків.

Прямі запити

Існує два методи виконання прямих запитів :

  • query використовується для операторів, які не вносять зміни, наприклад SELECT. Повертає об’єкт PDOStatemnt, з якого за допомогою методів fetch чи fetchAll витягаються результати запиту;
  • exec використовується для операторів ніби INSERT, DELETE чи UPDATE. Повертає число оброблених запитом рядків.

Прямі оператори використовуються тільки у тому випадку, якщо в запиті відсутні змінні і є упевненість, що запит безпечний і правильно екранований.

Підготовлені запити

PDO підтримує підготовлені запити (prepared statements), які корисні для захисту додатка від SQL-ін’єкцій: метод prepare виконує необхідні екранування.

Розглянемо приклад. Вимагається вставити властивості об’єкту Planet у таблицю Planets. Спочатку підготуємо запит:

$stmt = $pdo ->prepare ("INSERT INTO planets (name, color) VALUES (?, ?)");

Використовуємо метод prepare, який приймає як аргумент SQL-запит з псевдозмінними (placeholders). Псевдозмінні можуть бути двох типів: неіменовані і іменовані.

Неіменовані псевдозмінні

Неіменовані псевдозмінні (positional placeholders) відзначаються символом ?. Запит в результаті виходить компактним, але вимагається надати значення для підстановки, розміщені в тому ж порядку. Вони передаються у вигляді масиву через метод execute:

$stmt ->execute ([$planet ->name, $planet ->color]);

Іменовані псевдозмінні

При використанні іменованих псевдозмінних (named placeholders) порядок передачі значень для підстановки не важливий, але код в цьому випадку стає не таким компактним. У метод execute дані передаються у вигляді асоціативного масиву, в якому кожен ключ відповідає імені псевдозмінної, а значення масиву – значенню, яке вимагається підставити в запит. Переробимо попередній приклад:

$stmt = $pdo ->prepare ("INSERT INTO planets (name, color) VALUES (:name, :color)");
$stmt ->execute (['name' => $planet ->name, 'color' => $planet ->color]);

Методи prepare і execute використовуються як при виконанні запитів на зміну, так і при вибірці.

А інформацію про кількість оброблених рядків при необхідності надасть метод rowCount.

Управління поведінкою PDO при помилках

Параметр вибору режиму помилок PDO::ATTR_ERRMODE використовується для визначення поведінки PDO у разі помилок. Доступний три варіанти: PDO::ERRMODE_SILENT, PDO::ERRMODE_EXCEPTION і PDO::ERRMODE_WARNING.

PDO::ERRMODE_SILENT

Варіант за умовчанням. PDO просто запише інформацію про помилку, яку допоможуть отримати методи errorCode і errorInfo.

PDO::ERRMODE_EXCEPTION

Це переважний варіант, при якому на додаток до інформації про помилку PDO викидає виключення (PDOException). Виключення перериває виконання скрипта, що корисно при використанні транзакцій PDO. Приклад наведений нижче при описі транзакцій.

PDO::ERRMODE_WARNING

В цьому випадку PDO також записує інформацію про помилку. Потік виконання скрипта не уривається, але видаються попередження.

Методи bindValue і bindParam

Для підстановки значень в запиті можна також використати методи bindValue і bindParam. Перший зв’язує значення змінної з псевдозмінною, яка використана при підготовці запиту :

$stmt = $pdo ->prepare ("INSERT INTO planets (name, color) VALUES (:name, :color)");$stmt ->bindValue ('name', $planet ->name, PDO::PARAM_STR);

Зв’язали значення змінної $planet ->name з псевдозмінною :name. Зверніть увагу, що при використанні методів bindValue і bindParam як третій аргумент вказується тип змінної, використовуючи відповідні константи PDO. У прикладі – PDO::PARAM_STR.

Метод bindParam прив’язує змінну до псевдозмінної. В цьому випадку змінна пов’язана з псевдозмінним посиланням, а значення буде підставлено в запит тільки після виклику методу execute. Розглянемо на прикладі:

$stmt ->bindParam ('name', $planet ->name, PDO::PARAM_STR);

Транзакції в PDO

Транзакції дозволяють зберегти на деякий час і організувати виконання декількох запитів ” пакетом”. Запити, включені в транзакцію, застосовуються тільки у тому випадку, якщо при виконанні відсутні помилки. Транзакції підтримуються не усіма СУБД і працюють не з усіма SQL- конструкціями, оскільки деякі з них викликають неявне виконання. Список таких конструкцій можна знайти на сайті MariaDB.

Представимо незвичайний приклад. Користувачеві вимагається вибрати список планет, причому кожного разу при виконанні запиту поточні дані видаляються з бази, а потім вставляються нові. Якщо після видалення станеться помилка, то наступний користувач отримає порожній список. Щоб цього уникнути, використовуємо транзакції:

$pdo ->beginTransaction ();try { $stmt1 = $pdo ->exec ("DELETE FROM planets"); $stmt2 = $pdo ->prepare ("INSERT INTO planets (name, color) VALUES (?, ?)"); foreach ($planets as $planet) { $stmt2 ->execute ([$planet ->getName (), $planet ->getColor ()]); } $pdo ->commit ();} catch (PDOException $e) { $pdo ->rollBack ();}

Метод beginTransaction відключає автоматичне виконання запитів, а усередині конструкції try - catch запити виконуються в потрібному порядку. Якщо не виникне виключень PDOException, запити виконаються за допомогою методу commit. Інакше відкотяться за допомогою методу rollback, а автоматичне виконання запитів відновиться.

Таким чином з’явилася узгодженість виконання запитів. Очевидно, що для цього параметру PDO::ATTR_ERRMODE необхідно встановити значення PDO::ERRMODE_EXCEPTION.

Висновок

Тепер, коли робота з PDO описана, відмітимо його основні переваги:

  • з PDO легко перенести додаток на інші СУБД;
  • підтримуються усі популярні СУБД;
  • вбудована система управління помилками;
  • різноманітні варіанти представлення результатів вибірки;
  • підтримуються підготовлені запити, які скорочують код і роблять його стійким до SQL- ін’єкціям;
  • підтримуються транзакції, які допомагають зберегти цілісність даних і узгодженість запитів при паралельній роботі користувачів.

Переклад статті “How to configure and use PDO for database access on Linux”

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *