PDO — ulanish, xatolar rejimi, DSN

~15 daq · PHP · Ma'lumotlar bazasi

Muammodan boshlaylik

SQL ni terminalda yozishni o'rgandik. Lekin sayt foydalanuvchisi terminal ochmaydi — PHP o'zi bazaga so'rov yuborishi va natijani HTML ga aylantirishi kerak.

PHP da MySQL bilan ishlashning ikki yo'li bor: mysqli va PDO (PHP Data Objects). Bu kursda PDO ni o'rganamiz:

  • Bitta interfeys bilan MySQL, PostgreSQL, SQLite va boshqa bazalar bilan ishlaydi
  • Prepared statements qulay va xavfsiz
  • Xatolarni exception sifatida beradi

Ulanish

<?php
declare(strict_types=1);

$dsn = 'mysql:host=localhost;port=3306;dbname=dokon;charset=utf8mb4';
$foydalanuvchi = 'root';
$parol = '';

$pdo = new PDO($dsn, $foydalanuvchi, $parol, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
]);

echo "Ulandik!";

DSN nima?

DSN (Data Source Name) — bazaning "manzili" bitta qatorda:

mysql:host=localhost;port=3306;dbname=dokon;charset=utf8mb4
│     │              │         │            │
│     server         port      baza nomi    kodlash
baza turi

charset=utf8mb4 ni albatta yozing — aks holda o'zbekcha o', kirill harflari yoki emoji buzilib saqlanishi mumkin.

Boshqa bazalar uchun faqat DSN o'zgaradi:

'pgsql:host=localhost;dbname=dokon'     // PostgreSQL
'sqlite:' . __DIR__ . '/dokon.sqlite'   // SQLite — fayl

Uchta muhim sozlama

1. ERRMODE_EXCEPTION

Xato bo'lsa — PDOException tashlaydi. PHP 8 da bu standart, lekin aniq yozib qo'ygan ma'qul. Busiz (eski versiyalarda) noto'g'ri SQL jimgina false qaytarardi va xato topish soatlab vaqt olardi.

2. FETCH_ASSOC

Natija qatorlari assotsiativ massiv bo'lib keladi:

['id' => 1, 'nom' => 'Telefon', 'narx' => 3200000]

Busiz har bir qiymat ikki marta keladi — nom bilan ham, raqam bilan ham ([0 => 1, 'id' => 1, 1 => 'Telefon', 'nom' => 'Telefon', ...]).

3. EMULATE_PREPARES => false

So'rov parametrlari MySQL ning o'ziga yuboriladi (haqiqiy prepared statements). Sonlar ham son bo'lib qaytadi, string emas.


Ulanish xatosini ushlash

<?php

try {
    $pdo = new PDO($dsn, $foydalanuvchi, $parol, $sozlamalar);
} catch (PDOException $e) {
    error_log("DB ulanish xatosi: " . $e->getMessage());
    http_response_code(500);
    exit("Xizmat vaqtincha ishlamayapti. Birozdan keyin urinib ko'ring.");
}

[!WARNING] $e->getMessage() ni hech qachon foydalanuvchiga ko'rsatmang. Ulanish xatosining stack trace ida baza paroli ham bo'lishi mumkin (27-darsdagi misolni eslang). Logga yozing, foydalanuvchiga — umumiy xabar.


Birinchi so'rovlar

Parametrsiz, oddiy so'rov — query():

<?php

$natija = $pdo->query('SELECT id, nom, narx FROM mahsulotlar ORDER BY narx');

foreach ($natija as $qator) {
    echo "{$qator['nom']} — {$qator['narx']}" . PHP_EOL;
}

Natijani olish usullari

<?php

$stmt = $pdo->query('SELECT id, nom, narx FROM mahsulotlar');

$hammasi = $stmt->fetchAll();          // barcha qatorlar massivi
$bittasi = $stmt->fetch();             // bitta qator (yoki false)
$ustun = $stmt->fetchColumn();         // birinchi ustun qiymati

$soni = $pdo->query('SELECT COUNT(*) FROM mahsulotlar')->fetchColumn();
echo "Jami: $soni ta mahsulot";

$nomlar = $pdo->query('SELECT nom FROM mahsulotlar')->fetchAll(PDO::FETCH_COLUMN);
// ['Telefon', 'Noutbuk', ...]

$idBoyicha = $pdo->query('SELECT id, nom FROM mahsulotlar')->fetchAll(PDO::FETCH_KEY_PAIR);
// [1 => 'Telefon', 2 => 'Noutbuk', ...] — select uchun qulay

[!WARNING] query() ni faqat o'zgaruvchisiz, to'liq qo'lda yozilgan SQL uchun ishlating. Ichiga foydalanuvchidan kelgan biror narsa qo'shilsa — keyingi darsdagi prepare() ga o'ting. Bu muhokama qilinmaydigan qoida.


Sozlamalarni alohida faylga chiqarish

Parolni kod ichida saqlamang — kod Git ga tushadi.

config.php (Git ga qo'shilmaydi, .gitignore da):

<?php

return [
    'db' => [
        'dsn'    => 'mysql:host=localhost;dbname=dokon;charset=utf8mb4',
        'user'   => 'dokon_app',
        'parol'  => 'kuchli-parol-shu-yerda',
    ],
];

src/db.php:

<?php
declare(strict_types=1);

function db(): PDO
{
    static $pdo = null;   // bir marta ulanadi, keyin qayta ishlatiladi

    if ($pdo === null) {
        $config = require __DIR__ . '/../config.php';
        $pdo = new PDO($config['db']['dsn'], $config['db']['user'], $config['db']['parol'], [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ]);
    }

    return $pdo;
}

Har qanday sahifada:

<?php

require_once __DIR__ . '/../src/db.php';

$mahsulotlar = db()->query('SELECT * FROM mahsulotlar')->fetchAll();

static $pdo (16-dars) — bitta so'rov davomida ulanish bir marta ochiladi.

[!TIP] Ilova uchun root emas, alohida MySQL foydalanuvchisi yarating va unga faqat kerakli huquqlarni bering (SELECT, INSERT, UPDATE, DELETE faqat dokon bazasiga). Kod buzilsa ham, hujumchi butun MySQL serverni boshqara olmaydi. Batafsil — MySQL kursining 50-darsida.


Nima uchun shunday?

Nima uchun PHP da ikkita kutubxona (mysqli va PDO) bor?

Eski mysql_* funksiyalari (PHP 2-5) xavfsizlik va dizayn muammolari tufayli PHP 7 da butunlay olib tashlandi. O'rniga ikki yo'l paydo bo'ldi: mysqli — faqat MySQL uchun, uning barcha maxsus imkoniyatlari bilan; PDO — bazadan mustaqil umumiy interfeys. Ikkalasi ham xavfsiz. PDO kengroq tanlanadi, chunki bazani almashtirsangiz yoki testlarda SQLite ishlatsangiz — kod deyarli o'zgarmaydi.


Tez-tez uchraydigan xatolar

could not find driver — PHP da pdo_mysql kengaytmasi yoqilmagan. php.ini da extension=pdo_mysql qatoridagi ; ni olib tashlang.

SQLSTATE[HY000] [2002] Connection refused — MySQL serveri ishlamayapti yoki host/port noto'g'ri.

SQLSTATE[HY000] [1045] Access denied for user — login yoki parol noto'g'ri.

SQLSTATE[HY000] [1049] Unknown database 'dokon' — baza yaratilmagan.


Xulosa

Eslab qoling:

  • new PDO($dsn, $user, $parol, $sozlamalar) — ulanish
  • DSN da charset=utf8mb4 majburiy
  • ERRMODE_EXCEPTION, FETCH_ASSOC, EMULATE_PREPARES => false — har doim
  • fetchAll(), fetch(), fetchColumn() — natijani olish
  • query() — faqat o'zgaruvchisiz SQL uchun
  • Parol — alohida config faylda, Git dan tashqarida; ilova uchun root emas

Keyingi dars

36-dars: Prepared statements — SQL injectiondan himoya