PDO — ulanish, xatolar rejimi, DSN
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 darsdagiprepare()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
rootemas, alohida MySQL foydalanuvchisi yarating va unga faqat kerakli huquqlarni bering (SELECT, INSERT, UPDATE, DELETEfaqatdokonbazasiga). 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=utf8mb4majburiy ERRMODE_EXCEPTION,FETCH_ASSOC,EMULATE_PREPARES => false— har doimfetchAll(),fetch(),fetchColumn()— natijani olishquery()— faqat o'zgaruvchisiz SQL uchun- Parol — alohida config faylda, Git dan tashqarida; ilova uchun
rootemas
Keyingi dars
36-dars: Prepared statements — SQL injectiondan himoya