Як у PDO перейти з MySQL на PostgreSQL

PDO (PHP Data Objects) дозволяє змінити базу даних з MySQL на PostgreSQL без переписування PHP-коду, якщо код написаний правильно. Однак потрібно розуміти, що саме змінюється, а що — ні.


1. Головне правило

PDO прибирає залежність від драйвера БД, але не від SQL-діалекту.

  • ✔ PHP-код зазвичай не змінюється
  • ✔ змінюється DSN підключення
  • ❌ SQL-запити та структура БД можуть потребувати адаптації

2. Заміна DSN (обовʼязково)

Було: MySQL

$pdo = new PDO(
    "mysql:host=localhost;dbname=test_db;charset=utf8mb4",
    "db_user",
    "db_pass",
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]
);

Стало: PostgreSQL

$pdo = new PDO(
    "pgsql:host=localhost;port=5432;dbname=test_db",
    "db_user",
    "db_pass",
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]
);

Весь інший PHP-код залишається без змін.


3. PDO-код, який працює і в MySQL, і в PostgreSQL

$stmt = $pdo->prepare(
    "SELECT id, name FROM users WHERE active = :active"
);

$stmt->execute([
    'active' => true
]);

$rows = $stmt->fetchAll();

4. Що ламається при переході (і як виправити)

LIMIT offset, count

MySQL:

LIMIT 10, 20

PostgreSQL:

LIMIT 20 OFFSET 10

Універсальний варіант:

LIMIT :limit OFFSET :offset

AUTO_INCREMENT

MySQL:

id INT AUTO_INCREMENT

PostgreSQL:

id SERIAL
-- або
id INT GENERATED ALWAYS AS IDENTITY

PHP-код не змінюється, якщо використовується lastInsertId().


INSERT IGNORE

MySQL:

INSERT IGNORE INTO users (...)

PostgreSQL:

INSERT INTO users (...)
ON CONFLICT DO NOTHING

ON DUPLICATE KEY UPDATE

MySQL:

ON DUPLICATE KEY UPDATE name = VALUES(name)

PostgreSQL:

ON CONFLICT (email)
DO UPDATE SET name = EXCLUDED.name

IF()

MySQL:

IF(active = 1, 'yes', 'no')

PostgreSQL:

CASE WHEN active THEN 'yes' ELSE 'no' END

5. Типи даних, які потрібно змінити

MySQL PostgreSQL
TINYINT(1) BOOLEAN
DATETIME TIMESTAMP
LONGTEXT TEXT
DOUBLE DOUBLE PRECISION

6. JSON — різний синтаксис

MySQL:

JSON_EXTRACT(data, '$.name')

PostgreSQL:

data->>'name'

7. Рекомендовані налаштування PDO

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false
];

8. Коли можна змінити БД без змін PHP

  • ✔ використовується PDO
  • ✔ немає MySQL-специфічного SQL
  • ✔ відсутні FULLTEXT, ENUM, REPLACE INTO

9. Висновок

Перехід з MySQL на PostgreSQL у PDO — це перш за все:

  • 🔹 заміна DSN
  • 🔹 адаптація SQL-запитів
  • 🔹 міграція структури БД

PDO значно спрощує міграцію, але не є повною абстракцією від SQL.

Рекомендація: завжди пишіть портований SQL, щоб у майбутньому легко змінювати СУБД.

Як використовувати цикл for з PDO SELECT у PHP

PDO не дозволяє напряму перебирати результат запиту циклом for. Але цикл for можна використовувати, якщо попередньо отримати дані у вигляді масиву.


1. Основна ідея

Алгоритм такий:

  1. Виконати SELECT через PDO
  2. Отримати всі рядки методом fetchAll()
  3. Перебрати масив циклом for

2. Простий приклад SELECT + for

$stmt = $pdo->query(
    "SELECT id, name, email FROM users"
);

$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

for ($i = 0; $i < count($rows); $i++) {
    echo $rows[$i]['id'] . ' - ';
    echo $rows[$i]['name'] . ' - ';
    echo $rows[$i]['email'] . '<br>';
}

3. Оптимізований варіант (рекомендовано)

Щоб не викликати count() на кожній ітерації, краще зберегти кількість елементів у змінну:

$total = count($rows);

for ($i = 0; $i < $total; $i++) {
    echo $rows[$i]['name'] . '<br>';
}

4. SELECT з параметрами + for

$stmt = $pdo->prepare(
    "SELECT id, name FROM users WHERE active = :active"
);

$stmt->execute([
    'active' => 1
]);

$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

for ($i = 0, $c = count($users); $i < $c; $i++) {
    echo $users[$i]['id'] . ' - ';
    echo $users[$i]['name'] . '<br>';
}

5. Поширена помилка (НЕПРАВИЛЬНО)

for ($i = 0; $row = $stmt->fetch(); $i++) {
    // ❌ так робити не можна
}

Метод fetch() повертає false, а цикл for не призначений для такої логіки.


6. Кращі альтернативи циклу for

while (рекомендовано для великих вибірок)

while ($row = $stmt->fetch()) {
    echo $row['name'] . '<br>';
}

foreach (найчистіший код)

foreach ($users as $row) {
    echo $row['name'] . '<br>';
}

7. Коли варто використовувати for

  • ✔ Потрібен індекс елемента
  • ✔ Дані вже у масиві
  • ✔ Є логіка, залежна від позиції

Коли НЕ варто

  • ❌ Для великих SELECT-запитів
  • ❌ Коли не потрібен індекс

8. Висновок

Цикл for можна використовувати з PDO лише після fetchAll(). У більшості випадків foreach або while будуть простішими та ефективнішими.

Рекомендація: використовуйте for тільки тоді, коли це дійсно виправдано.

Повний гайд: як користуватися PDO в PHP

PDO (PHP Data Objects) — це сучасний, безпечний та універсальний спосіб роботи з базами даних у PHP. PDO підтримує MySQL, PostgreSQL, SQLite та інші СУБД і дозволяє писати портований код без прив’язки до конкретної бази.

У цій статті ви навчитеся користуватися PDO на практиці: від підключення до бази даних до виконання складних запитів і транзакцій.


1. Чому варто використовувати PDO

  • ✔ Захист від SQL-інʼєкцій
  • ✔ Prepared statements
  • ✔ Єдиний API для різних СУБД
  • ✔ Підтримка транзакцій
  • ✔ Зручна обробка помилок

2. Підключення до бази даних через PDO

<?php
$dsn  = "mysql:host=localhost;dbname=test_db;charset=utf8mb4";
$user = "db_user";
$pass = "db_password";

try {
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]);
} catch (PDOException $e) {
    die("Помилка підключення: " . $e->getMessage());
}

3. SELECT-запити

Простий SELECT

$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll();

SELECT з параметрами (prepared statement)

$stmt = $pdo->prepare(
    "SELECT * FROM users WHERE email = :email"
);

$stmt->execute([
    'email' => '[email protected]'
]);

$user = $stmt->fetch();

4. INSERT, UPDATE, DELETE

INSERT

$stmt = $pdo->prepare(
    "INSERT INTO users (name, email)
     VALUES (:name, :email)"
);

$stmt->execute([
    'name'  => 'Ivan',
    'email' => '[email protected]'
]);

$lastId = $pdo->lastInsertId();

UPDATE

$stmt = $pdo->prepare(
    "UPDATE users SET name = :name WHERE id = :id"
);

$stmt->execute([
    'name' => 'Petro',
    'id'   => 5
]);

DELETE

$stmt = $pdo->prepare(
    "DELETE FROM users WHERE id = :id"
);

$stmt->execute([
    'id' => 10
]);

5. Плейсхолдери в PDO

Іменовані

WHERE id = :id

Позиційні

WHERE id = ?
$stmt->execute([5]);

6. Типи даних та bindValue

Для числових значень, LIMIT та OFFSET рекомендується явно вказувати тип:

$stmt = $pdo->prepare(
    "SELECT * FROM users
     WHERE active = :active
     LIMIT :limit OFFSET :offset"
);

$stmt->bindValue(':active', true, PDO::PARAM_BOOL);
$stmt->bindValue(':limit', 20, PDO::PARAM_INT);
$stmt->bindValue(':offset', 0, PDO::PARAM_INT);

$stmt->execute();

7. Отримання результатів

  • fetch() — один рядок
  • fetchAll() — всі рядки
  • rowCount() — не рекомендовано для SELECT

8. Транзакції

try {
    $pdo->beginTransaction();

    $pdo->exec(
        "UPDATE accounts
         SET balance = balance - 100
         WHERE id = 1"
    );

    $pdo->exec(
        "UPDATE accounts
         SET balance = balance + 100
         WHERE id = 2"
    );

    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    echo "Помилка транзакції";
}

9. Обробка помилок

try {
    $pdo->query("SELECT * FROM unknown_table");
} catch (PDOException $e) {
    echo $e->getMessage();
}

10. Типові помилки при роботі з PDO

  • ❌ Конкатенація змінних у SQL
  • ❌ Відсутність prepared statements
  • ❌ ATTR_EMULATE_PREPARES = true
  • ❌ rowCount() для SELECT

11. PDO у WordPress

Хоча WordPress використовує $wpdb, PDO можна застосовувати:

  • у власних плагінах
  • у REST API
  • у CLI-скриптах

12. Висновок

PDO — це стандарт для сучасного PHP. Використання prepared statements, транзакцій та портованого SQL робить код безпечним, масштабованим і готовим до майбутніх змін.

Рекомендація: завжди використовуйте PDO у нових PHP-проєктах.

Change permissions for uploaded files in WordPress

Sometimes WordPress set permission “640” for uploaded files and photos. With this permissions file not show on website.

For correct permissions “644” add this code to functions.php of your WordPress theme:

add_filter( 'wp_handle_upload', 'fix_uploaded_file_permissions' );

function fix_uploaded_file_permissions( $fileinfo ) {
    @chmod( $fileinfo['file'], 0644 );
    return $fileinfo;
}

Як в MySQL видалити дані з таблиці SQL запитом

У MySQL можна повністю очистити таблицю за допомогою двох основних команд:

1. DELETE (повільний, зберігає структуру та AUTO_INCREMENT)

DELETE FROM table_name;

Видаляє всі рядки, але зберігає структуру таблиці.Якщо в таблиці є AUTO_INCREMENT, лічильник не скидається.Для прискорення можна використати DELETE FROM table_name WHERE 1=1;

2. TRUNCATE TABLE (швидкий, скидає AUTO_INCREMENT)

TRUNCATE TABLE table_name;

Швидший за DELETE, бо не записує операції для кожного рядка в лог транзакцій.Скидає AUTO_INCREMENT до 1.Не можна використовувати з таблицями, що мають зовнішні ключі (FOREIGN KEY), якщо FOREIGN_KEY_CHECKS=1.

Який варіант вибрати?

  • Якщо потрібно просто очистити дані та зберегти AUTO_INCREMENT – DELETE.
  • Якщо потрібна швидкість і можна скинути AUTO_INCREMENT – TRUNCATE.

Якщо ж потрібно видалити саму таблицю разом із її структурою:

DROP TABLE table_name;

Ця команда повністю видалить таблицю, тому потім доведеться створювати її заново.

Якщо таблиця має зовнішні ключі, перед TRUNCATE чи DELETE потрібно вимкнути перевірку:

SET FOREIGN_KEY_CHECKS=0;
TRUNCATE TABLE table_name;
SET FOREIGN_KEY_CHECKS=1;

Ось кілька прикладів PHP-коду, що виконують очищення таблиці в MySQL за допомогою DELETE, TRUNCATE і DROP TABLE.

1. Використання DELETE

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test_db";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Помилка підключення: " . $conn->connect_error);
}

$sql = "DELETE FROM table_name";  // Видаляє всі дані, але зберігає структуру
if ($conn->query($sql) === TRUE) {
    echo "Дані успішно видалені!";
} else {
    echo "Помилка: " . $conn->error;
}

$conn->close();
?>

2. Використання TRUNCATE TABLE (швидше, скидає AUTO_INCREMENT)

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test_db";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Помилка підключення: " . $conn->connect_error);
}

$sql = "TRUNCATE TABLE table_name";  // Швидко очищає таблицю
if ($conn->query($sql) === TRUE) {
    echo "Таблиця очищена!";
} else {
    echo "Помилка: " . $conn->error;
}

$conn->close();
?>

3. Використання DROP TABLE (видаляє таблицю повністю)

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test_db";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Помилка підключення: " . $conn->connect_error);
}

$sql = "DROP TABLE table_name";  // Видаляє таблицю повністю
if ($conn->query($sql) === TRUE) {
    echo "Таблиця видалена!";
} else {
    echo "Помилка: " . $conn->error;
}

$conn->close();
?>

Який варіант вибрати?

  • DELETE – якщо потрібно очистити всі записи, зберігаючи AUTO_INCREMENT.
  • TRUNCATE TABLE – якщо потрібно швидко очистити таблицю і скинути AUTO_INCREMENT.
  • DROP TABLE – якщо потрібно повністю видалити таблицю.

💡 Якщо таблиця має FOREIGN KEY, перед TRUNCATE або DELETE додайте:

$conn->query("SET FOREIGN_KEY_CHECKS=0");
$conn->query("TRUNCATE TABLE table_name");
$conn->query("SET FOREIGN_KEY_CHECKS=1");

Робота з посиланнями другого рівня через .htaccess

Щоб .htaccess обробляв посилання другого рівня вкладення (наприклад, /url1/url2) і перенаправляв їх на url1/url2.php або на 404.php, потрібно внести наступні зміни в код, наведений в статті “Робота з Url у файлі .htaccess

Оновлений .htaccess файл:

RewriteEngine On

# Якщо файл або директорія існують, пропускаємо запит
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Перевіряємо, чи є запит виду /url1/url2
RewriteCond %{REQUEST_URI} ^/([^/]+/[^/]+)$
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(.*)$ /$1.php [L]

# Перевіряємо, чи є запит виду /url
RewriteCond %{REQUEST_URI} ^/([^/]+)$
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(.*)$ /$1.php [L]

# Якщо файл не існує, перенаправляємо на 404.php
RewriteCond %{REQUEST_URI} ^/([^/]+(/[^/]+)?)$
RewriteCond %{DOCUMENT_ROOT}/%1.php !-f
RewriteRule ^(.*)$ /404.php [L]

Як це працює:

Обробка вкладених URL (/url1/url2):

Умова RewriteCond %{REQUEST_URI} ^/([^/]+/[^/]+)$ перевіряє, чи є URL двох рівнів.
Умова RewriteCond %{DOCUMENT_ROOT}/$1.php -f перевіряє, чи існує файл url1/url2.php.

Обробка однорівневих URL (/url):

Умова RewriteCond %{REQUEST_URI} ^/([^/]+)$ перевіряє, чи є URL одного рівня.
Умова RewriteCond %{DOCUMENT_ROOT}/$1.php -f перевіряє, чи існує файл url.php.

Обробка 404:

Якщо жоден з файлів не знайдено, перенаправляємо запит на 404.php.

Додаткові поради:

Якщо потрібна обробка більшої глибини вкладення (наприклад, /url1/url2/url3), можна використовувати регулярний вираз із зірочкою:
RewriteCond %{REQUEST_URI} ^/([^/]+(/[^/]+)*)$

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

RewriteLogLevel 3

Перевірте, чи працюють запити на двох рівнях, і чи коректно обробляються запити, які не знаходять відповідних файлів.

Робота з Url у файлі .htaccess

Напишемо скріпт в файлі .htaccess який буде переадресовувати запити /url на відповідний PHP файл url.php, а якщо файл url.php не існує, то переадресовувати на файл 404.php

RewriteEngine On

# Перевіряємо, чи запит йде до URL виду /url
RewriteCond %{REQUEST_URI} ^/([^/]+)$
RewriteCond %{DOCUMENT_ROOT}/%1.php -f
RewriteRule ^(.*)$ /$1.php [L]

# Якщо файл не існує, переадресовуємо на 404.php
RewriteCond %{REQUEST_URI} ^/([^/]+)$
RewriteCond %{DOCUMENT_ROOT}/%1.php !-f
RewriteRule ^(.*)$ /404.php [L]

Як це працює:
Перша умова:

RewriteCond %{REQUEST_URI} ^/([^/]+)$ — перевіряє, чи є запит до URL вигляду /url.
RewriteCond %{DOCUMENT_ROOT}/%1.php -f — перевіряє, чи існує файл url.php у кореневій директорії.
Якщо умови виконуються, URL перенаправляється на відповідний файл PHP.

Друга умова:

Якщо файл url.php не існує, переадресовуємо запит на 404.php.

Додатково:

Переконайтеся, що у вас ввімкнений модуль mod_rewrite у конфігурації сервера Apache.
Якщо 404.php повинен знаходитися у певному підкаталозі, змініть шлях у відповідному правилі.

Помилка 500 Internal Server Error зазвичай виникає через помилки у конфігурації .htaccess. Давайте перевіримо код і виправимо можливі проблеми.
Оновлений .htaccess файл

RewriteEngine On

# Якщо файл або директорія існують, пропускаємо запит
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Перевіряємо, чи є запит типу /url
RewriteCond %{REQUEST_URI} ^/([^/]+)$
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(.*)$ /$1.php [L]

# Якщо файл не існує, переадресовуємо на 404.php
RewriteCond %{REQUEST_URI} ^/([^/]+)$
RewriteCond %{DOCUMENT_ROOT}/$1.php !-f
RewriteRule ^(.*)$ /404.php [L]

Зміни:

Перша перевірка:

 Додано правило, яке дозволяє серверу обробляти запити до існуючих файлів та директорій без втручання mod_rewrite.

Перевірка імен файлів:

Забезпечено правильну перевірку на існування файлу PHP.

Скориговано умови:

Умова RewriteCond %{DOCUMENT_ROOT}/%1.php -f правильно обробляє файли.

Дії для виправлення:
Перевірте наявність модуля mod_rewrite:

У файлі конфігурації Apache (зазвичай httpd.conf або apache2.conf), переконайтеся, що модуль увімкнено:
sudo a2enmod rewrite
sudo systemctl restart apache2

Перевірте дозволи на файли:

Усі файли та директорії повинні мати відповідні права доступу. Наприклад:
chmod 644 *.php

Увімкніть відображення помилок Apache.
Увімкніть журнал помилок, щоб побачити точну причину:

LogLevel debug
ErrorLog ${APACHE_LOG_DIR}/error.log

Перевірте конфігурацію Apache
Переконайтеся, що віртуальний хост дозволяє використання .htaccess:

<Directory /path/to/your/site>
    AllowOverride All
</Directory>

Після внесення змін перезапустіть сервер Apache:

sudo systemctl restart apache2

Якщо проблема залишається, перегляньте журнал помилок Apache (/var/log/apache2/error.log або аналогічний залежно від вашої ОС) для детального діагностування.

Стрілкові функції в JavaScript

Стрілкові функції в JavaScript – це не просто скорочений синтаксис, а справжні магічні стріли, які можуть зробити ваш код більш читабельним та зручним. Ось декілька цікавих речей, які ви можете робити з ними:

Короткі функції з одним виразом: Стрілкові функції ідеально підходять для коротких функцій, які повертають один результат. Наприклад:

    const numbers = [1, 2, 3, 4, 5];
    const squared = numbers.map((num) => num ** 2);
    console.log(squared); // Виведе [1, 4, 9, 16, 25]

    Callback-функції: Вони дуже зручні для використання в методах масивів, таких як mapfilterreduce:

    const numbers = [1, 2, 3, 4, 5];
    const squared = numbers.map((num) => num ** 2);
    console.log(squared); // Виведе [1, 4, 9, 16, 25]
    

    Лексичне прив’язування this: Стрілкові функції автоматично прив’язують this до контексту, в якому вони були створені. Це уникне багатьох проблем, зокрема при роботі з об’єктами та подіями.

    Ланцюги обіцянок (Promises): Стрілкові функції можна використовувати в асинхронних операціях:

    const fetchData = async () => {
        try {
            const response = await fetch('https://api.example.com/data');
            const data = await response.json();
            console.log(data);
        } catch (error) {
            console.error('Something went wrong:', error);
        }
    };
    

    Параметри за замовчуванням та розпакування: Ви можете використовувати параметри за замовчуванням та розпаковування:

    const greet = (name = 'Friend') => `Hello, ${name}!`;
    console.log(greet()); // Виведе "Hello, Friend!"
    

    Отже, стрілкові функції – це не просто стріли, вони – ваші надійні помічники у світі JavaScript! 🚀1 2 3 4 5.

    How to install SSH2 extension for PHP 7.4

    If You have an error like: Call to undefined function ssh2_connect() that’s mean that in your PHP not installed ssh2 extension.

    Install this extension is very simple, in Ubuntu Linux or other Debian based distribution just use the command:

    sudo apt install php7.4-ssh2

    This command will install ssh2 extension to Your PHP 7.4. If you use other PHP version, just change number in this command to Your version.

    Create first program “Hello World” on Assembler in Linux

    To create program on Assembler in Linux operating system we need compiler for x86 architecture named nasm

    sudo apt install nasm

    To compile program in file hello.asm use commands:

    nasm -felf64 hello.asm
    ld hello.o
    

    To start compiled program use command:

    ./a.out

    Nov let’s create our program in file hello.asm using text editor vim

    vim hello.asm

    Here is the full code of program:

    message:
    	db	"Hello World!", 10
    
    _start:
    	mov	rax, 1
    	mov	rdi, 1
    	mov	rsi, message
    	mov	rdx, 14
    	syscall
    
    	mov	rax, 60
    	syscall
    
    	section .data
    
    global _start
    
    section .text

    In first two lines we create some label which contains some directive db width string “Hello World!“, and last symbol 10line feed. Directive is like variables in high level programming languages.

    Directive db can use 1 byte for writing information and can write constants from -128 to 255 and can write symbols and strings.

    Next create label _start: and width command mov we write some data in processor address (register):

    mov rax, 1 – move 1 in register rax – in this string we call data recording.
    mov rdi, 1 – This register response for input and output stream. When we send 1 in register rdi we say processor to work width standard output stream.
    mov rsi, message – we write address of our string in register rsi.
    mov rdx, 14 – we write count bytes of our string in register rdx.

    Next we make system call width command syscall.

    mov rax, 60 – we put number 60 in register rax – this is system call for exit.

    And again make system call width command syscall.

    At the end of program we run command section .data

    Width command global _start we run our program from label _start.

    Width command section .text we declare text part of our code