16-bo‘lim
PDO va ma'lumotlar bazasi
PDO bilan ulanish, tayyorlangan so'rovlar, natijani o'qish, tranzaksiyalar va SQL in'eksiyadan himoya.
Ushbu bo‘lim mundarijasi
Ma'lumotlar bazasi - veb ilovaning yuragi. PHP unga PDO (PHP Data Objects) orqali murojaat qiladi.
Bu bo'limdagi barcha misollar haqiqiy SQLite bazasida ishga tushirilgan - shuning uchun ularni o'zingiz ham darhol takrorlashingiz mumkin.
Ulanish #
// Har bir misol qat'iy turlar rejimida ishlaydi.
// Misollar uchun xotiradagi SQLite bazasidan foydalanamiz.
function bazaYarat(): PDO
{
$pdo = new PDO("sqlite::memory:", null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$pdo->exec("
CREATE TABLE mahsulotlar (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
narx INTEGER NOT NULL,
soni INTEGER NOT NULL DEFAULT 0,
turkum TEXT NOT NULL
)
");
$qatorlar = [
["Klaviatura", 35000000, 12, "aksessuar"],
["Sichqoncha", 12000000, 40, "aksessuar"],
["Monitor 24", 240000000, 5, "monitor"],
["Monitor 27", 390000000, 2, "monitor"],
["Quloqchin", 8900000, 0, "aksessuar"],
];
$stmt = $pdo->prepare(
"INSERT INTO mahsulotlar (nom, narx, soni, turkum) VALUES (?, ?, ?, ?)"
);
foreach ($qatorlar as $q) {
$stmt->execute($q);
}
return $pdo;
}
// Haqiqiy loyihada ulanish shunday bo'ladi:
//
// $dsn = "mysql:host=localhost;dbname=dokon;charset=utf8mb4";
// $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,
// ]);
$pdo = bazaYarat();
echo "Ulandi. Drayver: " . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . "\n";
echo "\nMuhim uchta sozlama:\n";
$sozlamalar = [
["ERRMODE_EXCEPTION", "Xato bo'lsa istisno tashlaydi", "Aks holda JIMGINA false qaytaradi"],
["FETCH_ASSOC", "Natija assotsiativ massiv", "Standart holatda har qator IKKI marta keladi"],
["EMULATE_PREPARES=false", "Haqiqiy tayyorlangan so'rov", "Taqlid qilingani zaifroq"],
];
foreach ($sozlamalar as [$nom, $nima, $nimaga]) {
printf(" %-24s %s\n", $nom, $nima);
printf(" %-24s (%s)\n", "", $nimaga);
}
$soni = $pdo->query("SELECT COUNT(*) FROM mahsulotlar")->fetchColumn();
echo "\nJadvalda {$soni} ta yozuv bor.\n";
Ulandi. Drayver: sqlite
Muhim uchta sozlama:
ERRMODE_EXCEPTION Xato bo'lsa istisno tashlaydi
(Aks holda JIMGINA false qaytaradi)
FETCH_ASSOC Natija assotsiativ massiv
(Standart holatda har qator IKKI marta keladi)
EMULATE_PREPARES=false Haqiqiy tayyorlangan so'rov
(Taqlid qilingani zaifroq)
Jadvalda 5 ta yozuv bor.
ERRMODE_EXCEPTION ni har doim yoqingStandart holatda PDO xatoni jimgina o'tkazib
yuboradi va false qaytaradi:
$stmt = $pdo->prepare("SELECT * FROM yoq_jadval");
$stmt->execute(); // false - lekin hech narsa aytmaydi
$qatorlar = $stmt->fetchAll(); // Fatal error: null da fetchAll
Xato haqiqiy sababdan uzoqda ko'rinadi va uni topish soatlab vaqt oladi.
ERRMODE_EXCEPTION bilan esa darhol aniq xabar
keladi: SQLSTATE[HY000]: General error: 1 no such
table: yoq_jadval.
EMULATE_PREPARES => false ham muhim: taqlid rejimida
PDO parametrlarni o'zi so'rovga qo'shadi, haqiqiy
rejimda esa ularni bazaga alohida yuboradi.
Ikkinchisi xavfsizroq va turlarni ham to'g'ri saqlaydi:
taqlid rejimida LIMIT ? ga son bergan bo'lsangiz ham
u qo'shtirnoqda ketadi va MySQL xato beradi.
Va charset=utf8mb4 ni DSN da albatta ko'rsating -
aks holda emoji va ba'zi belgilar buziladi.
Tayyorlangan so'rovlar #
$pdo = bazaYarat();
// 1. Pozitsion parametrlar - ? belgisi
$stmt = $pdo->prepare("SELECT nom, narx FROM mahsulotlar WHERE turkum = ? AND soni > ?");
$stmt->execute(["aksessuar", 0]);
echo "Pozitsion parametrlar bilan:\n";
foreach ($stmt as $q) {
printf(" %-14s %12s\n", $q["nom"], number_format($q["narx"] / 100, 0, ",", " "));
}
// 2. Nomli parametrlar - o'qishga qulayroq
echo "\nNomli parametrlar bilan:\n";
$stmt = $pdo->prepare("
SELECT nom, narx, soni
FROM mahsulotlar
WHERE narx BETWEEN :eng_kam AND :eng_kop
ORDER BY narx DESC
");
$stmt->execute(["eng_kam" => 10000000, "eng_kop" => 300000000]);
foreach ($stmt->fetchAll() as $q) {
printf(" %-14s %12s %2d dona\n", $q["nom"],
number_format($q["narx"] / 100, 0, ",", " "), $q["soni"]);
}
// 3. Bitta qiymat olish
echo "\n";
$stmt = $pdo->prepare("SELECT COUNT(*) FROM mahsulotlar WHERE turkum = ?");
$stmt->execute(["monitor"]);
echo "Monitorlar soni: " . $stmt->fetchColumn() . "\n";
// 4. Bitta qator olish
$stmt = $pdo->prepare("SELECT * FROM mahsulotlar WHERE id = ?");
$stmt->execute([1]);
$bitta = $stmt->fetch();
echo "id=1: " . ($bitta === false ? "topilmadi" : $bitta["nom"]) . "\n";
$stmt->execute([999]);
$yoq = $stmt->fetch();
echo "id=999: " . ($yoq === false ? "topilmadi" : $yoq["nom"]) . "\n";
Pozitsion parametrlar bilan:
Klaviatura 350 000
Sichqoncha 120 000
Nomli parametrlar bilan:
Monitor 24 2 400 000 5 dona
Klaviatura 350 000 12 dona
Sichqoncha 120 000 40 dona
Monitorlar soni: 2
id=1: Klaviatura
id=999: topilmadi
SQL in'eksiyani amalda ko'rish #
$pdo = bazaYarat();
$hujum = "' OR '1'='1";
// 1. XAVFLI usul - satrni birlashtirish
$xavfliSql = "SELECT nom FROM mahsulotlar WHERE nom = '" . $hujum . "'";
echo "Xavfli so'rov:\n {$xavfliSql}\n";
$natija = $pdo->query($xavfliSql)->fetchAll();
echo " Qaytgan qatorlar: " . count($natija) . " ta <- HAMMASI!\n";
// 2. XAVFSIZ usul - tayyorlangan so'rov
echo "\nXavfsiz so'rov:\n SELECT nom FROM mahsulotlar WHERE nom = ?\n";
$stmt = $pdo->prepare("SELECT nom FROM mahsulotlar WHERE nom = ?");
$stmt->execute([$hujum]);
$natija = $stmt->fetchAll();
echo " Qaytgan qatorlar: " . count($natija) . " ta <- to'g'ri\n";
echo " (chunki bunday NOMLI mahsulot yo'q)\n";
echo "\n";
echo "Farq shundaki, ikkinchi holatda \"' OR '1'='1\" - bu\n";
echo "SQL buyrug'i emas, shunchaki QIDIRILAYOTGAN MATN.\n";
Xavfli so'rov:
SELECT nom FROM mahsulotlar WHERE nom = '' OR '1'='1'
Qaytgan qatorlar: 5 ta <- HAMMASI!
Xavfsiz so'rov:
SELECT nom FROM mahsulotlar WHERE nom = ?
Qaytgan qatorlar: 0 ta <- to'g'ri
(chunki bunday NOMLI mahsulot yo'q)
Farq shundaki, ikkinchi holatda "' OR '1'='1" - bu
SQL buyrug'i emas, shunchaki QIDIRILAYOTGAN MATN.
Tayyorlangan so'rov faqat qiymatlarni himoya qiladi. Bu ishlamaydi:
$stmt = $pdo->prepare("SELECT * FROM mahsulotlar ORDER BY ? ?");
$stmt->execute([$ustun, $yonalish]); // XATO
Sabab: baza so'rovni oldindan tahlil qiladi va o'shanda jadval bilan ustun nomlari ma'lum bo'lishi kerak.
Shuning uchun ular uchun oq ro'yxat (8-bo'lim) ishlatiladi:
$ruxsat = ["nom", "narx", "soni"];
$ustun = in_array($_GET["tartib"] ?? "", $ruxsat, true)
? $_GET["tartib"]
: "nom";
$yonalish = ($_GET["yonalish"] ?? "") === "desc" ? "DESC" : "ASC";
$sql = "SELECT * FROM mahsulotlar ORDER BY {$ustun} {$yonalish}";
Bu yerda {$ustun} to'g'ridan-to'g'ri qo'yilyapti - va
bu xavfsiz, chunki uning qiymati faqat uch
variantdan biri bo'lishi mumkin.
Xuddi shu holat LIMIT uchun ham amal qiladi ba'zi
drayverlarda: sonni (int) ga aylantirib qo'ying.
Yozish va tranzaksiyalar #
$pdo = bazaYarat();
// INSERT va oxirgi id
$stmt = $pdo->prepare(
"INSERT INTO mahsulotlar (nom, narx, soni, turkum) VALUES (:nom, :narx, :soni, :turkum)"
);
$stmt->execute([
"nom" => "Veb-kamera", "narx" => 45000000, "soni" => 8, "turkum" => "aksessuar",
]);
$yangiId = (int) $pdo->lastInsertId();
echo "Qo'shildi, id = {$yangiId}\n";
// UPDATE va ta'sirlangan qatorlar
$stmt = $pdo->prepare("UPDATE mahsulotlar SET narx = narx * 9 / 10 WHERE turkum = ?");
$stmt->execute(["monitor"]);
echo "Chegirma qo'llandi: " . $stmt->rowCount() . " qatorga\n";
// DELETE
$stmt = $pdo->prepare("DELETE FROM mahsulotlar WHERE soni = 0");
$stmt->execute();
echo "O'chirildi: " . $stmt->rowCount() . " qator\n";
echo "\n";
// TRANZAKSIYA - yo hammasi, yo hech nima
echo "Tranzaksiya sinovi (ombordan ikkita mahsulot ko'chirish):\n";
try {
$pdo->beginTransaction();
$kamaytir = $pdo->prepare("UPDATE mahsulotlar SET soni = soni - ? WHERE id = ?");
$kamaytir->execute([5, 1]);
echo " 1-qadam: 5 dona ayirildi\n";
// Ikkinchi qadamda ataylab xato keltiramiz
$qoldiq = $pdo->query("SELECT soni FROM mahsulotlar WHERE id = 1")->fetchColumn();
if ($qoldiq < 10) {
throw new RuntimeException("Omborda yetarli emas (qoldi: {$qoldiq})");
}
$pdo->commit();
echo " Tasdiqlandi\n";
} catch (Throwable $x) {
$pdo->rollBack();
echo " XATO: " . $x->getMessage() . "\n";
echo " Orqaga qaytarildi\n";
}
$qoldiq = $pdo->query("SELECT soni FROM mahsulotlar WHERE id = 1")->fetchColumn();
echo "\nYakuniy qoldiq: {$qoldiq} (o'zgarmadi - tranzaksiya bekor qilindi)\n";
Qo'shildi, id = 6
Chegirma qo'llandi: 2 qatorga
O'chirildi: 1 qator
Tranzaksiya sinovi (ombordan ikkita mahsulot ko'chirish):
1-qadam: 5 dona ayirildi
XATO: Omborda yetarli emas (qoldi: 7)
Orqaga qaytarildi
Yakuniy qoldiq: 12 (o'zgarmadi - tranzaksiya bekor qilindi)
try/catch/finally bilan o'rangTranzaksiya atomarlikni kafolatlaydi: bir necha amal birga bajariladi yoki birortasi ham bajarilmaydi.
Klassik misol - pul o'tkazish:
$pdo->beginTransaction();
try {
$ayir->execute([$summa, $kimdan]);
$qosh->execute([$summa, $kimga]);
$pdo->commit();
} catch (Throwable $x) {
$pdo->rollBack();
throw $x; // yuqoriga uzatamiz
}
Birinchi UPDATE bajarilib, ikkinchisi xato bersa -
pul yo'q bo'lib ketardi. rollBack() buni oldini
oladi.
Uch muhim eslatma:
| Eslatma | Sabab |
|---|---|
rollBack() dan keyin istisnoni qayta tashlang | Aks holda xato yashiriladi |
DDL (CREATE, ALTER) tranzaksiyani avtomatik tasdiqlaydi | MySQL da |
| Tranzaksiyani qisqa tuting | Uzun tranzaksiya qatorlarni qulflaydi |
MyISAM jadval turi tranzaksiyani umuman qo'llab-quvvatlamaydi - MySQL da InnoDB ishlating.
Natijani obyektga aylantirish #
$pdo = bazaYarat();
final class Mahsulot
{
public function __construct(
public readonly int $id,
public readonly string $nom,
public readonly int $narx,
public readonly int $soni,
public readonly string $turkum,
) {}
public function narxMatni(): string
{
return number_format($this->narx / 100, 0, ",", " ") . " so'm";
}
public function omborda(): bool
{
return $this->soni > 0;
}
}
$stmt = $pdo->query("SELECT id, nom, narx, soni, turkum FROM mahsulotlar ORDER BY narx");
$mahsulotlar = [];
foreach ($stmt as $q) {
$mahsulotlar[] = new Mahsulot(
(int) $q["id"],
$q["nom"],
(int) $q["narx"],
(int) $q["soni"],
$q["turkum"],
);
}
printf("%-14s %14s %8s %s\n", "NOM", "NARX", "SONI", "HOLAT");
echo str_repeat("-", 50) . "\n";
foreach ($mahsulotlar as $m) {
printf("%-14s %14s %8d %s\n", $m->nom, $m->narxMatni(), $m->soni,
$m->omborda() ? "mavjud" : "tugagan");
}
echo "\n";
$jami = array_sum(array_map(
static fn(Mahsulot $m): int => $m->narx * $m->soni,
$mahsulotlar
));
echo "Ombor qiymati: " . number_format($jami / 100, 0, ",", " ") . " so'm\n";
echo "Obyektlar soni: " . count($mahsulotlar) . "\n";
NOM NARX SONI HOLAT
--------------------------------------------------
Quloqchin 89 000 so'm 0 tugagan
Sichqoncha 120 000 so'm 40 mavjud
Klaviatura 350 000 so'm 12 mavjud
Monitor 24 2 400 000 so'm 5 mavjud
Monitor 27 3 900 000 so'm 2 mavjud
Ombor qiymati: 28 800 000 so'm
Obyektlar soni: 5
Bazadan kelgan qiymatlar hammasi satr bo'lishi
mumkin (drayverga bog'liq). Yuqoridagi kodda
(int) $q["narx"] aylantirish aynan shuning uchun.
Obyektga aylantirish uch foyda beradi:
| Foyda | Izoh |
|---|---|
| Turlar to'g'ri | int - int, string - string |
| Xatti-harakat bir joyda | narxMatni() takrorlanmaydi |
| Muharrir yordam beradi | $m->nmo xato darhol ko'rinadi |
PDO ning FETCH_CLASS rejimi ham bor:
$stmt->fetchAll(PDO::FETCH_CLASS, Mahsulot::class);
Lekin u konstruktorni chetlab o'tib xossalarga
to'g'ridan-to'g'ri yozadi - ya'ni readonly va
tekshiruvlar ishlamaydi. Shuning uchun yuqoridagi
oshkora usul afzalroq.
Katta loyihalarda bu ish ORM (Doctrine, Eloquent) tomonidan bajariladi.
- SQLite bazasiga ulaning va jadval yarating.
ERRMODE_EXCEPTIONsiz mavjud bo'lmagan jadvalga murojaat qiling.- Uni yoqing va farqni ko'ring.
- Pozitsion va nomli parametrlar bilan ikkita so'rov yozing.
' OR '1'='1ni satr birlashtirish bilan sinang.- Xuddi shuni tayyorlangan so'rov bilan takrorlang.
ORDER BY ?yozib ko'ring - ishladimi?- Saralash ustuni uchun oq ro'yxat qo'llang.
- Tranzaksiya ichida xato keltirib,
rollBack()ni sinang. - Natija qatorlarini obyektlarga aylantiring.
Xulosa #
- PDO ni uchta sozlama bilan yarating:
ERRMODE_EXCEPTION,FETCH_ASSOC,EMULATE_PREPARES=false. - Standart holatda PDO xatoni jimgina o'tkazadi - xato haqiqiy sababdan uzoqda ko'rinadi.
- DSN da
charset=utf8mb4ni ko'rsating. - Tayyorlangan so'rov tuzilma va ma'lumotni alohida yuboradi - in'eksiya shuning uchun ishlamaydi.
- Foydalanuvchi qiymatini SQL ga hech qachon birlashtirmang.
- Jadval va ustun nomlarini parametrlashtirib bo'lmaydi - ular uchun oq ro'yxat kerak.
fetch()topilmasafalseqaytaradi -=== falsebilan tekshiring.lastInsertId()yangi yozuvning id sini,rowCount()ta'sirlangan qatorlar sonini beradi.- Tranzaksiya atomarlikni kafolatlaydi: yo hammasi, yo hech nima.
rollBack()dan keyin istisnoni qayta tashlang; tranzaksiyani qisqa tuting.- MySQL da InnoDB ishlating - MyISAM tranzaksiyani qo'llab-quvvatlamaydi.
- Natijani obyektga aylantiring: turlar to'g'ri bo'ladi va xatti-harakat bir joyda turadi.
Keyingi bo'limda xavfsizlikni to'liq ko'rib chiqamiz - XSS, CSRF va parol xeshlash.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.