Tarkibga o'tish

3.6 PHP'dan bazaga ulanish (PDO)

⬅️ Oldingi: 3.5 Jadvallarni bog'lash (JOIN) Β· 🏠 README Β· Keyingi: 3.7 PostgreSQL va MySQL'dan farqlari ➑️


Hozirgacha SQL'ni phpMyAdmin'da qo'lda yozdik. Lekin haqiqiy dasturda PHP kodi bazaga ulanib, SQL buyruqlarini yuborishi kerak. Mana shu β€” PHP va ma'lumotlar bazasini birlashtiradigan eng muhim qadam. Buning uchun PDO degan vositadan foydalanamiz.

PDO (PHP Data Objects) β€” PHP'ni ma'lumotlar bazasiga ulaydigan standart, xavfsiz vosita.

Bazaga ulanish

<?php
$pdo = new PDO(
    "mysql:host=localhost;dbname=maktab;charset=utf8mb4",
    "root",   // foydalanuvchi nomi (XAMPP'da odatda "root")
    ""        // parol (XAMPP'da odatda bo'sh)
);

Tushuntiramiz: - new PDO(...) β€” bazaga ulanish obyektini yaratamiz (PDO β€” class, biz undan obyekt yaratamiz). - "mysql:host=localhost;dbname=maktab;..." β€” ulanish ma'lumotlari: mysql turi, localhost (shu kompyuter), dbname=maktab (qaysi baza). Bu satr DSN (Data Source Name β€” "ma'lumot manbasi nomi") deb ataladi. - "root" va "" β€” foydalanuvchi va parol. XAMPP'da standart sozlama: foydalanuvchi root, parol bo'sh.

Agar ulanish xato bersa, MySQL XAMPP'da ishlab turganini tekshiring va baza nomi to'g'riligiga ishonch hosil qiling.

Xato rejimini yoqing β€” ERRMODE_EXCEPTION (eng muhim!)

Yuqoridagi ulanish ishlaydi, lekin bitta jiddiy kamchiligi bor: agar so'rovda xato bo'lsa (ustun nomi noto'g'ri, jadval yo'q, baza o'chgan) β€” PDO standart holatda jim turadi. Hech narsa demaydi, faqat false qaytaradi. Natijada dasturingiz "sababsiz" ishlamay qoladi, siz esa nima xato bo'lganini bilmaysiz. Bu β€” boshlovchilarni eng ko'p aldaydigan tuzoq.

Yechim: PDO'ga "xato bo'lsa, baqir!" deb buyuramiz. Buni setAttribute bilan qilamiz:

<?php
$pdo = new PDO("mysql:host=localhost;dbname=maktab;charset=utf8mb4", "root", "");

// Xato bo'lsa β€” PDO "exception" (istisno) tashlaydi, jim turmaydi
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Endi xato bo'lsa, PDO PDOException degan istisno tashlaydi (istisnolarni 2.10 β€” "Xatolarni boshqarish" bobida o'rgangansiz). Uni try/catch bilan ushlaymiz:

<?php
try {
    $pdo = new PDO("mysql:host=localhost;dbname=maktab;charset=utf8mb4", "root", "");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Ataylab noto'g'ri jadval nomi β€” xato chiqishini ko'rish uchun
    $pdo->query("SELECT * FROM mavjud_emas_jadval");  // ❌ bunday jadval yo'q

} catch (PDOException $e) {
    echo "Baza bilan muammo: " . $e->getMessage();
}

Nima sodir bo'ldi: - try { ... } ichida β€” bazaga oid kod. Agar xato bo'lsa, PHP darhol catchga "sakraydi". - catch (PDOException $e) β€” bazadan kelgan xatoni ushlaydi. $e->getMessage() β€” xatoning aniq matni (masalan, "Table 'maktab.mavjud_emas_jadval' doesn't exist"). - Endi siz aniq nima xato bo'lganini ko'rasiz. Bu β€” debug (xato topish)ni yuz barobar osonlashtiradi.

Oltin qoida: har bir PDO ulanishida darhol setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) yozing. Busiz xatolar yashirin qoladi va sizni ovora qiladi. Bu β€” professional PHP kodining birinchi belgisi.

Diqqat: $e->getMessage() ichida ba'zan baza tuzilishi haqida ma'lumot bo'ladi. Tayyor (production) saytda bu matnni foydalanuvchiga to'g'ridan-to'g'ri ko'rsatmang β€” uni log faylga yozing, foydalanuvchiga esa oddiy "Xatolik yuz berdi" xabarini chiqaring (Xavfsizlik bobida batafsil).

Ulanishni BITTA joyda saqlash β€” db.php

Diqqat qilgan bo'lsangiz, har bir misolda ulanish satrini (new PDO(...) + setAttribute) qayta-qayta yozyapmiz. Bu β€” yomon odat: parol o'zgarsa, hamma fayllarni tahrirlash kerak bo'ladi. To'g'ri yo'l β€” ulanishni bitta joyda, alohida faylda yozish va kerak bo'lganda chaqirish.

db.php degan fayl yarating:

<?php
// db.php β€” bazaga ulanishni bir joyda saqlaydigan fayl

function ulan(): PDO
{
    static $pdo = null;   // bir marta ulanadi, keyin eslab qoladi

    if ($pdo === null) {
        $pdo = new PDO(
            "mysql:host=localhost;dbname=maktab;charset=utf8mb4",
            "root",
            ""
        );
        // Har doim shu uchta sozlama:
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
        $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    }

    return $pdo;
}

Endi istalgan faylda shunchaki chaqiramiz:

<?php
require 'db.php';        // ulanish funksiyasini ulaymiz

$pdo = ulan();           // ulanishni olamiz

$natija = $pdo->query("SELECT * FROM talabalar");
foreach ($natija as $qator) {
    echo $qator['ism'] . "<br>";
}

Tushuntiramiz: - function ulan(): PDO β€” funksiya PDO obyekti qaytaradi (: PDO β€” qaytish turi, 1.9'da o'rgangansiz). - static $pdo = null β€” static o'zgaruvchi funksiya chaqiruvlari orasida qiymatini eslab qoladi. Shuning uchun ulan() ni 10 marta chaqirsangiz ham, baza bir marta ulanadi (har safar yangi ulanish ochish β€” sekin va isrofgarchilik). - Uchta sozlama har doim birga yuradi: - ATTR_ERRMODE β€” xatoni istisno qilib tashlaydi (yuqorida ko'rdik). - ATTR_DEFAULT_FETCH_MODE β€” natijani standart holatda kalitli massiv qiladi (pastda batafsil). - ATTR_EMULATE_PREPARES => false β€” prepared statement'ni haqiqiy (baza darajasida) ishlatadi, soxta emas. Bu xavfsizroq.

Bu naqsh β€” boshqa har bir misolda ham ishlatiladi. Endilikda "require 'db.php'; $pdo = ulan();" degan kod β€” bazaga ulanishning standart usuli.

Ma'lumot o'qish β€” query

Oddiy o'qish so'rovini query bilan yuboramiz va natijani foreach bilan aylanib chiqamiz:

<?php
require 'db.php';
$pdo = ulan();

$natija = $pdo->query("SELECT * FROM talabalar");

foreach ($natija as $qator) {
    echo $qator['ism'] . " - " . $qator['yosh'] . " yosh<br>";
}

Nima sodir bo'ldi: - $pdo->query("SELECT ...") β€” bazaga so'rov yuboradi va natijani qaytaradi. - Natija β€” qatorlar to'plami. Har bir qator β€” kalitli massiv (1.8'da o'rgangan!), kalitlari ustun nomlari (ism, yosh). - foreach bilan har bir qatorni olamiz, $qator['ism'] orqali ustun qiymatini o'qiymiz.

Ko'ryapsizmi β€” bu yerda hamma narsa birlashdi: PHP (foreach, massiv) + SQL (SELECT) + baza. Bazadagi ma'lumot endi PHP kodingizga keldi.

Eslatma: query() faqat foydalanuvchi ma'lumoti yo'q sof so'rovlar uchun (masalan "barcha talabalarni ber"). Agar so'rovga tashqaridan kelgan qiymat qo'shadigan bo'lsangiz β€” pastda ko'radiganimizdek prepare/execute ishlating.

Bir qatorni o'qish usullari β€” fetch, fetchAll, fetchColumn

foreach β€” qatorlarni aylanishning bir yo'li. Lekin PDO bizga natijani olishning bir nechta qulay usulini beradi. Eng muhim uchtasi:

1) fetch() β€” bitta qator oladi. Keyingi chaqiruvda β€” keyingi qator. Qator qolmasa false qaytaradi. while bilan juda qulay:

<?php
require 'db.php';
$pdo = ulan();

$stmt = $pdo->query("SELECT ism, yosh FROM talabalar");

while ($qator = $stmt->fetch()) {
    echo $qator['ism'] . " - " . $qator['yosh'] . "<br>";
}

2) fetchAll() β€” barcha qatorlarni bitta massivga oladi. Natija β€” massivlar massivi. Bu sonni count() bilan bilish yoki butun ro'yxatni saqlash uchun qulay:

<?php
require 'db.php';
$pdo = ulan();

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

echo "Jami: " . count($talabalar) . " ta talaba<br>";

foreach ($talabalar as $t) {
    echo $t['ism'] . "<br>";
}

3) fetchColumn() β€” bitta qiymat (bitta ustun) oladi. COUNT(*), MAX(...), yoki bitta ustun kerak bo'lganda eng qulay:

<?php
require 'db.php';
$pdo = ulan();

// Talabalar soni β€” bitta son
$soni = $pdo->query("SELECT COUNT(*) FROM talabalar")->fetchColumn();
echo "Talabalar soni: $soni<br>";

// Eng katta yosh
$kattaYosh = $pdo->query("SELECT MAX(yosh) FROM talabalar")->fetchColumn();
echo "Eng katta yosh: $kattaYosh";

Qachon qaysi biri? Bitta qiymat kerak (son, summa) β†’ fetchColumn(). Bitta qator kerak (bitta talaba) β†’ fetch(). Hamma qatorlar kerak (ro'yxat) β†’ fetchAll() yoki foreach. Katta jadvalda fetchAll butun jadvalni xotiraga yuklaydi β€” juda katta ma'lumotda while ($stmt->fetch()) tejamliroq.

Natija ko'rinishi β€” FETCH rejimlari (ASSOC, OBJ, CLASS)

PDO natijani uch xil ko'rinishda bera oladi. Buni "fetch rejimi" deb ataymiz. db.php'da standart qilib FETCH_ASSOC ni qo'ydik, lekin har bir chaqiruvda boshqasini tanlash mumkin.

1) PDO::FETCH_ASSOC β€” kalitli massiv (eng ko'p ishlatiladi). Ustunga $qator['ism'] bilan murojaat qilamiz:

<?php
require 'db.php';
$pdo = ulan();

$qator = $pdo->query("SELECT * FROM talabalar LIMIT 1")
             ->fetch(PDO::FETCH_ASSOC);

echo $qator['ism'];     // massiv: kvadrat qavs bilan

2) PDO::FETCH_OBJ β€” obyekt. Ustunlarga $qator->ism (strelka) bilan murojaat qilamiz β€” ba'zilarga bu chiroyliroq tuyuladi:

<?php
require 'db.php';
$pdo = ulan();

$qator = $pdo->query("SELECT * FROM talabalar LIMIT 1")
             ->fetch(PDO::FETCH_OBJ);

echo $qator->ism;       // obyekt: strelka bilan
echo " - ";
echo $qator->yosh;

3) PDO::FETCH_CLASS β€” o'zingiz yozgan class obyektiga. Bu β€” eng kuchlisi. Bazadagi qatorni to'g'ridan-to'g'ri o'z class obyektingizga aylantiradi. Ustun nomlari class xususiyatlariga mos kelishi kerak:

<?php
require 'db.php';
$pdo = ulan();

// Avval class yozamiz (zamonaviy, tiplangan uslubda)
class Talaba
{
    public int $id = 0;
    public string $ism = '';
    public int $yosh = 0;
    public string $shahar = '';

    public function tanishtir(): string
    {
        return "{$this->ism}, {$this->yosh} yosh, {$this->shahar}dan";
    }
}

// Bazadagi qatorni Talaba obyektiga aylantiramiz
$stmt = $pdo->query("SELECT * FROM talabalar LIMIT 1");
$talaba = $stmt->fetchObject(Talaba::class);

echo $talaba->ism;             // Ali
echo "<br>";
echo $talaba->tanishtir();     // Ali, 18 yosh, Toshkentdan

Tushuntiramiz: - fetchObject(Talaba::class) β€” qatorni Talaba class obyekti qilib qaytaradi. - Endi $talaba β€” oddiy massiv emas, to'liq obyekt: undan tanishtir() kabi metodlarni chaqira olasiz. Bu β€” bazani obyektga bog'laydigan kuchli usul (kelajakda "ORM" deb ataladigan kutubxonalar shu asosda ishlaydi).

Hammasini bittada β€” barcha talabalarni Talaba obyektlari sifatida olish:

<?php
require 'db.php';
$pdo = ulan();

$stmt = $pdo->query("SELECT * FROM talabalar");
$talabalar = $stmt->fetchAll(PDO::FETCH_CLASS, Talaba::class);

foreach ($talabalar as $t) {        // har bir $t β€” Talaba obyekti
    echo $t->tanishtir() . "<br>";
}

Qaysi birini tanlash? Oddiy sahifalar uchun FETCH_ASSOC (massiv) yetarli va eng ko'p ishlatiladi. Class va metodlar bilan ishlasangiz β€” FETCH_CLASS toza, obyektga yo'naltirilgan kod beradi.

Foydalanuvchi ma'lumoti bilan ishlash β€” XAVFSIZLIK

Endi eng muhim mavzu. Ko'pincha so'rovga foydalanuvchidan kelgan ma'lumot qo'shamiz. Masalan, "id'si shu bo'lgan talabani top". Buni noto'g'ri qilish β€” jiddiy xavfsizlik teshigiga olib keladi.

❌ XAVFLI usul β€” hech qachon bunday qilmang:

<?php
$id = $_GET['id'];   // foydalanuvchidan kelgan ma'lumot
// ❌ XAVFLI: foydalanuvchi ma'lumotini to'g'ridan-to'g'ri so'rovga qo'shish
$natija = $pdo->query("SELECT * FROM talabalar WHERE id = $id");

Nega xavfli? Chunki foydalanuvchi $id o'rniga zararli SQL kodini kiritishi mumkin. Bu β€” SQL injection degan hujum. Yomon niyatli odam shu orqali butun bazangizni o'qishi yoki o'chirishi mumkin. Bu β€” eng keng tarqalgan va xavfli xatolardan biri.

βœ… TO'G'RI usul β€” "prepared statement" (tayyorlangan so'rov):

<?php
$id = $_GET['id'];

// 1) So'rovni "tayyorlaymiz" β€” qiymat o'rniga ? belgisi qo'yamiz
$stmt = $pdo->prepare("SELECT * FROM talabalar WHERE id = ?");

// 2) Qiymatni alohida, xavfsiz tarzda beramiz
$stmt->execute([$id]);

// 3) Natijani olamiz
$talaba = $stmt->fetch();

echo $talaba['ism'];

Tushuntiramiz: - prepare("... WHERE id = ?") β€” so'rovni tayyorlaymiz, qiymat o'rniga ? (savol belgisi) qo'yamiz. - execute([$id]) β€” qiymatni alohida beramiz. PHP uni xavfsiz tarzda joylaydi β€” endi zararli kod ishlamaydi. - fetch() β€” bitta qatorni oladi (fetchAll() β€” barcha qatorlarni).

Asosiy qoida: foydalanuvchidan kelgan har qanday ma'lumot so'rovga ? orqali, prepare/execute bilan qo'shilishi shart. Hech qachon to'g'ridan-to'g'ri so'rov ichiga yozmang. Bu β€” sizning bazangizni himoyalaydi.

Quyidagi diagramma butun oqimni ko'rsatadi: PHP qiymatni PDO'ga beradi, PDO uni prepared statement bilan bazaga xavfsiz yuboradi va natijani PHP'ga qaytaradi:

PDO ulanish oqimi: PHP, PDO, baza va prepared statement

Nomli parametrlar β€” :ism va bindValue

? belgisi yaxshi, lekin so'rovda ko'p qiymat bo'lsa, qaysi ? qaysi qiymatga tegishli ekanini chalkashtirish oson (tartibni aniq saqlash kerak). Bunday holatda nomli parametrlar ancha o'qilishli: ? o'rniga :ism, :yosh kabi nom qo'yamiz.

<?php
require 'db.php';
$pdo = ulan();

$stmt = $pdo->prepare(
    "SELECT * FROM talabalar WHERE shahar = :shahar AND yosh > :yosh"
);

// Qiymatlarni nom bo'yicha beramiz β€” tartibi muhim emas
$stmt->execute([
    ':shahar' => 'Toshkent',
    ':yosh'   => 18,
]);

foreach ($stmt->fetchAll() as $t) {
    echo $t['ism'] . "<br>";
}

Tushuntiramiz: - So'rovda ? o'rniga :shahar, :yosh (ikki nuqta bilan boshlanadigan nom) qo'ydik. - execute([...]) ga kalitli massiv beramiz: kalit β€” parametr nomi, qiymat β€” uning qiymati. Tartib emas, nom muhim.

bindValue β€” qiymatni alohida bog'lash. execute([...]) ichida hammasini bittada berish o'rniga, har bir qiymatni alohida bog'lash ham mumkin. Bu, ayniqsa, qiymat turini aniq aytmoqchi bo'lganda foydali:

<?php
require 'db.php';
$pdo = ulan();

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

// Qiymatni nomga bog'laymiz, turini ham aytamiz (butun son)
$stmt->bindValue(':id', $_GET['id'], PDO::PARAM_INT);

$stmt->execute();
$talaba = $stmt->fetch();
print_r($talaba);

Tushuntiramiz: - bindValue(':id', $qiymat, PDO::PARAM_INT) β€” :id parametriga qiymatni bog'laydi va "bu β€” butun son" deb belgilaydi. - PDO::PARAM_INT β€” butun son, PDO::PARAM_STR β€” matn, PDO::PARAM_BOOL β€” mantiqiy. Ko'pincha bularsiz ham ishlaydi, lekin turni aniq berish toza va xavfsiz. - bindValue dan keyin execute() ni qiymatsiz chaqiramiz (qiymatlar allaqachon bog'langan).

Xulosa: kam qiymat β†’ ? qulay. Ko'p qiymat β†’ :nom o'qilishliroq. Tur muhim β†’ bindValue(..., PDO::PARAM_INT). Hammasi bir xil darajada xavfsiz β€” muhimi, qiymatni hech qachon so'rov matniga to'g'ridan-to'g'ri yozmaslik.

Ma'lumot qo'shish (xavfsiz)

<?php
require 'db.php';
$pdo = ulan();

$ism = "Yangi Talaba";
$yosh = 18;
$shahar = "Toshkent";

$stmt = $pdo->prepare("INSERT INTO talabalar (ism, yosh, shahar) VALUES (?, ?, ?)");
$stmt->execute([$ism, $yosh, $shahar]);

echo "Talaba qo'shildi!";

Bir nechta qiymat bo'lsa, har biriga bitta ? qo'yamiz va executega massiv sifatida (tartibda) beramiz.

Yangi qator id'sini olish β€” lastInsertId()

INSERT qilganingizda baza yangi qatorga avtomatik id beradi (AUTO_INCREMENT). Ko'pincha shu yangi id darhol kerak bo'ladi β€” masalan, "talaba qo'shildi, endi uning sahifasiga yo'naltir". lastInsertId() aynan shuni beradi:

<?php
require 'db.php';
$pdo = ulan();

$stmt = $pdo->prepare("INSERT INTO talabalar (ism, yosh, shahar) VALUES (?, ?, ?)");
$stmt->execute(["Bekzod", 19, "Andijon"]);

$yangiId = $pdo->lastInsertId();    // yangi qo'shilgan qatorning id'si
echo "Yangi talaba qo'shildi, id = $yangiId";

lastInsertId() β€” oxirgi INSERT'da berilgan id'ni qaytaradi. Shuning uchun uni execute() dan darhol keyin chaqiring.

Nechta qator o'zgardi β€” rowCount()

UPDATE yoki DELETE qilganingizda, "nechta qatorga ta'sir qildi?" degan savol tug'iladi. rowCount() shu sonni beradi:

<?php
require 'db.php';
$pdo = ulan();

$stmt = $pdo->prepare("UPDATE talabalar SET shahar = ? WHERE shahar = ?");
$stmt->execute(["Toshkent shahri", "Toshkent"]);

echo $stmt->rowCount() . " ta qator yangilandi";

DELETE da ham xuddi shunday:

<?php
require 'db.php';
$pdo = ulan();

$stmt = $pdo->prepare("DELETE FROM talabalar WHERE yosh < ?");
$stmt->execute([10]);

if ($stmt->rowCount() === 0) {
    echo "Hech qaysi qator o'chmadi.";
} else {
    echo $stmt->rowCount() . " ta talaba o'chirildi.";
}

Diqqat: rowCount() UPDATE/DELETE/INSERT uchun ishonchli. SELECT da qatorlar sonini bilish uchun fetchAll() qilib count() ishlatgan yaxshi (ba'zi bazalarda SELECT'da rowCount to'g'ri ishlamaydi).

Transaksiyalar β€” "yo hammasi, yo hech narsa"

Ba'zan bir nechta so'rov birga bajarilishi shart β€” biri bo'lib, ikkinchisi bo'lmasa, ma'lumot buziladi. Eng klassik misol β€” pul o'tkazish: bir hisobdan pul yechish va boshqasiga qo'shish. Tasavvur qiling:

  1. Ali hisobidan 300 so'm yechildi. βœ…
  2. ...keyin to'satdan tok o'chdi / xato chiqdi. ❌
  3. Vali hisobiga 300 so'm qo'shilmadi.

Natija: 300 so'm havoga uchdi! Ali pulni yo'qotdi, Vali olmadi. Bu β€” falokat.

Transaksiya shu muammoni hal qiladi: bir nechta so'rovni bitta bo'lak qilib bog'laydi. Qoida β€” "yo hammasi bajariladi, yo hech narsa". Birortasi xato bo'lsa β€” hammasi bekor qilinadi (orqaga qaytariladi), baza o'zgarmaydi.

Buning uchun uchta buyruq bor: - beginTransaction() β€” "transaksiyani boshla" (bu yerdan keyingi o'zgarishlar vaqtincha, tasdiqlanmagan). - commit() β€” "hammasi joyida, o'zgarishlarni tasdiqla" (endi baza haqiqatan o'zgaradi). - rollBack() β€” "xato bo'ldi, hammasini bekor qil" (baza boshlang'ich holatiga qaytadi).

<?php
require 'db.php';
$pdo = ulan();

$jonatuvchiId   = 1;     // Ali
$qabulQiluvchiId = 2;    // Vali
$summa = 300;

try {
    $pdo->beginTransaction();   // transaksiya boshlandi

    // 1) Jo'natuvchidan pul yechamiz
    $yech = $pdo->prepare("UPDATE hisoblar SET balans = balans - :s WHERE id = :id");
    $yech->execute([':s' => $summa, ':id' => $jonatuvchiId]);

    // 2) Qabul qiluvchiga pul qo'shamiz
    $qosh = $pdo->prepare("UPDATE hisoblar SET balans = balans + :s WHERE id = :id");
    $qosh->execute([':s' => $summa, ':id' => $qabulQiluvchiId]);

    $pdo->commit();             // hammasi joyida β€” tasdiqlaymiz
    echo "Pul muvaffaqiyatli o'tkazildi!";

} catch (PDOException $e) {
    $pdo->rollBack();           // xato β€” hammasini bekor qilamiz
    echo "Xatolik: o'tkazma bekor qilindi. Pul joyida qoldi.";
}

Tushuntiramiz: - Ikkala UPDATE birga bajarilishi shart. beginTransaction() ulanrni "bitta bo'lak"ka bog'laydi. - Agar ikkinchi UPDATE (yoki oradagi biror narsa) xato bersa β€” catchga sakraymiz, rollBack() birinchi UPDATEni ham bekor qiladi. Ali pulini yo'qotmaydi. - Faqat ikkalasi ham muvaffaqiyatli bo'lsa β€” commit() o'zgarishlarni tasdiqlaydi.

Hayotiy qoida: bir-biriga bog'liq, "hammasi yoki hech narsa" bo'lishi kerak bo'lgan o'zgarishlar (pul, buyurtma + ombor, ko'p jadvalni birga yangilash) doim transaksiya ichida bo'lsin. ATTR_ERRMODE => EXCEPTION bu yerda shart, chunki xato bo'lganini bilmasak, rollBack ham qila olmaymiz.

Mashqlar

Bu mashqlar uchun maktab bazasi va talabalar jadvali tayyor bo'lsin. Fayllarni htdocs/darslar ichida yarating, http://localhost/... orqali oching. Yangi mashqlar uchun db.php faylini yaratib, require 'db.php'; $pdo = ulan(); naqshidan foydalaning.

Oson 1. PDO bilan maktab bazasiga ulaning (xato bo'lmasligini tekshiring). 2. query bilan barcha talabalarni o'qing va foreach bilan ismlarini chiqaring. 3. Har bir talabaning ism va yoshini birga chiqaring. 4. prepare/execute bilan id'si 1 bo'lgan talabani toping va chiqaring. 5. prepare/execute bilan yangi talaba qo'shing. 6. Ulanishga setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) qo'shing, keyin ataylab noto'g'ri jadval nomi bilan so'rov yuborib, try/catch orqali xato matnini chiqaring. 7. fetchColumn() bilan talabalar sonini (SELECT COUNT(*)) chiqaring.

O'rta 8. Faqat Toshkentdagi talabalarni o'qing (WHERE shahar = ? bilan, prepared). 9. fetchAll() bilan barcha talabalarni massivga oling va sonini (count) chiqaring. 10. mahsulotlar jadvalidagi barcha mahsulotlarni narxi bilan chiqaring. 11. Foydalanuvchidan kelgan $_GET['id'] bo'yicha talabani xavfsiz (prepared) qidiring. 12. Bir talabaning yoshini UPDATE + prepared statement bilan o'zgartiring, keyin rowCount() bilan nechta qator yangilanganini chiqaring. 13. db.php faylini yarating: ulan() funksiyasi PDO obyekti qaytarsin (uch sozlama bilan). Boshqa faylda require 'db.php' qilib ishlating. 14. Yangi talaba qo'shing va lastInsertId() bilan unga berilgan yangi id'ni chiqaring. 15. :shahar va :yosh nomli parametrlari bilan "Toshkentdagi, yoshi 18 dan katta" talabalarni qidiring.

Qiyin 16. To'liq "talabalar ro'yxati" sahifasini yarating: bazadan barcha talabalarni o'qib, ularni HTML jadval (<table>) ko'rinishida chiqaring. (<table>, <tr>, <td> teglaridan foydalaning β€” internetdan ko'ring.) 17. SQL injection xavfini amalda tushuntiring: nima uchun "WHERE id = $id" xavfli va "WHERE id = ?" xavfsiz ekanini o'z so'zingiz bilan yozing. 18. JOIN'ni PHP'dan ishlating: kitoblar va mualliflar ni JOIN qilib, har bir kitob nomi va muallifini PHP bilan o'qib chiqaring. 19. hisoblar jadvalini yarating (id, egasi, balans) va transaksiya bilan bir hisobdan ikkinchisiga pul o'tkazing. Atayin xato qo'shib (masalan, ikkinchi UPDATE'da mavjud bo'lmagan ustun nomi), rollBack ishlaganini β€” pul joyida qolganini ko'rsating. 20. Talaba classi yozing (tiplangan xususiyatlar + tanishtir() metodi) va FETCH_CLASS/fetchAll(PDO::FETCH_CLASS, ...) bilan barcha talabalarni obyektlar sifatida olib, har biri uchun tanishtir() ni chaqiring.

Yechim β€” 6 (xato rejimi va try/catch)
<?php
try {
    $pdo = new PDO("mysql:host=localhost;dbname=maktab;charset=utf8mb4", "root", "");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Ataylab noto'g'ri jadval β€” xato chiqishini ko'ramiz
    $pdo->query("SELECT * FROM yoq_bunday_jadval");   // ❌

} catch (PDOException $e) {
    echo "Baza xatosi: " . $e->getMessage();
    // Masalan: Table 'maktab.yoq_bunday_jadval' doesn't exist
}

ERRMODE_EXCEPTION yoqilmaganda bu so'rov jim false qaytarardi va siz nima xato bo'lganini bilmasdingiz. Endi PDO aniq xabar beradi va siz uni try/catch bilan boshqarasiz. Bu β€” har bir loyihada birinchi qo'shadigan sozlama.

Yechim β€” 13 (db.php β€” bir joyda ulanish)

db.php:

<?php
function ulan(): PDO
{
    static $pdo = null;

    if ($pdo === null) {
        $pdo = new PDO(
            "mysql:host=localhost;dbname=maktab;charset=utf8mb4",
            "root",
            ""
        );
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
        $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    }

    return $pdo;
}

Ishlatish (royxat.php):

<?php
require 'db.php';
$pdo = ulan();

foreach ($pdo->query("SELECT * FROM talabalar") as $t) {
    echo $t['ism'] . "<br>";
}

static $pdo tufayli ulan() ni necha marta chaqirsangiz ham baza bir marta ulanadi. Parol o'zgarsa β€” faqat db.php ni tahrirlaysiz, qolgan fayllar tegmaydi.

Yechim β€” 14 (lastInsertId)
<?php
require 'db.php';
$pdo = ulan();

$stmt = $pdo->prepare("INSERT INTO talabalar (ism, yosh, shahar) VALUES (?, ?, ?)");
$stmt->execute(["Dilnoza", 17, "Namangan"]);

$yangiId = $pdo->lastInsertId();
echo "Qo'shildi! Yangi id = $yangiId";

lastInsertId() ni execute() dan darhol keyin chaqiramiz β€” u oxirgi INSERT'da berilgan avtomatik id'ni qaytaradi. Bu, masalan, "yangi yozuv sahifasiga yo'naltir" yoki "bog'liq jadvalga shu id bilan yozuv qo'sh" uchun kerak.

Yechim β€” 16 (talabalar ro'yxati sahifasi)
<?php
require 'db.php';
$pdo = ulan();
$natija = $pdo->query("SELECT * FROM talabalar ORDER BY ism");
?>

<table border="1" cellpadding="8">
    <tr>
        <th>ID</th>
        <th>Ism</th>
        <th>Yosh</th>
        <th>Shahar</th>
    </tr>
    <?php foreach ($natija as $qator): ?>
        <tr>
            <td><?= $qator['id'] ?></td>
            <td><?= $qator['ism'] ?></td>
            <td><?= $qator['yosh'] ?></td>
            <td><?= $qator['shahar'] ?></td>
        </tr>
    <?php endforeach; ?>
</table>

Yangi narsa: <?= ... ?>. Bu β€” <?php echo ... ?> ning qisqa shakli. HTML ichida PHP qiymatini chiqarish uchun qulay. <?= $qator['ism'] ?> degani β€” "shu yerga ismni chiqar".

Bu misol PHP va HTML'ni birlashtiradi: PHP bazadan ma'lumot oladi, HTML uni chiroyli jadval qilib ko'rsatadi. Bu β€” haqiqiy veb-sahifaning oddiy ko'rinishi!

Yechim β€” 17 (SQL injection nega xavfli)

"WHERE id = $id" xavfli, chunki foydalanuvchi $id o'rniga oddiy son emas, zararli SQL kiritishi mumkin. Masalan, manzilga ?id=0 OR 1=1 yozsa, so'rov shunday bo'ladi:

SELECT * FROM talabalar WHERE id = 0 OR 1=1

1=1 har doim rost β€” natijada barcha talabalar qaytadi. Yomonroq holatda hujumchi DROP TABLE, parollarni o'qish kabi buyruqlar qo'shishi mumkin.

"WHERE id = ?" (prepared statement) xavfsiz, chunki:

<?php
$stmt = $pdo->prepare("SELECT * FROM talabalar WHERE id = ?");
$stmt->execute([$id]);

Bu yerda $id alohida, "ma'lumot" sifatida yuboriladi β€” u hech qachon "buyruq" (SQL kod) sifatida bajarilmaydi. Foydalanuvchi 0 OR 1=1 yozsa ham, PHP uni butun matn sifatida id ga qo'yadi, kod sifatida emas. Shuning uchun qoida: foydalanuvchi ma'lumotini hech qachon so'rov matniga to'g'ridan-to'g'ri qo'shmang β€” doim ? va execute([...]) ishlating.

Yechim β€” 18 (PHP'dan JOIN)

<?php
require 'db.php';
$pdo = ulan();

$sql = "SELECT kitoblar.nom, mualliflar.ism
        FROM kitoblar
        JOIN mualliflar ON kitoblar.muallif_id = mualliflar.id";

$natija = $pdo->query($sql);

foreach ($natija as $qator) {
    echo $qator['nom'] . " β€” " . $qator['ism'] . "<br>";
}
// O'tkan kunlar β€” Abdulla Qodiriy
// Mehrobdan chayon β€” Abdulla Qodiriy
// Sarob β€” Abdulla Qahhor
JOIN'li so'rov PHP'da ham xuddi oddiy SELECT kabi ishlaydi: query bilan yuboramiz, foreach bilan aylanib chiqamiz. Har bir $qator ikkala jadvaldan kelgan ustunlarni (nom, ism) o'z ichiga oladi. (Bu so'rovda foydalanuvchi ma'lumoti yo'q, shuning uchun query yetadi; bo'lsa β€” prepare/execute ishlatardik.)

Yechim β€” 19 (transaksiya bilan pul o'tkazish)

Avval jadval (phpMyAdmin'da bir marta):

CREATE TABLE hisoblar (
    id INT AUTO_INCREMENT PRIMARY KEY,
    egasi VARCHAR(100),
    balans INT
);
INSERT INTO hisoblar (egasi, balans) VALUES ('Ali', 1000), ('Vali', 500);

PHP:

<?php
require 'db.php';
$pdo = ulan();

$summa = 300;

try {
    $pdo->beginTransaction();

    // 1) Alidan yechamiz
    $pdo->prepare("UPDATE hisoblar SET balans = balans - ? WHERE id = ?")
        ->execute([$summa, 1]);

    // 2) Valiga qo'shamiz
    $pdo->prepare("UPDATE hisoblar SET balans = balans + ? WHERE id = ?")
        ->execute([$summa, 2]);

    $pdo->commit();
    echo "O'tkazildi!";

} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Xato β€” bekor qilindi. Pul joyida.";
}

Agar ikkinchi UPDATE'da ataylab xato qilsangiz (masalan balanss deb noto'g'ri ustun yozsangiz), ERRMODE_EXCEPTION tufayli istisno tashlanadi, catchga o'tadi va rollBack() birinchi UPDATE'ni ham bekor qiladi β€” Ali pulini yo'qotmaydi. Aynan shu β€” transaksiyaning kuchi: "yo hammasi, yo hech narsa".

Yechim β€” 20 (FETCH_CLASS bilan obyektlar)
<?php
require 'db.php';
$pdo = ulan();

class Talaba
{
    public int $id = 0;
    public string $ism = '';
    public int $yosh = 0;
    public string $shahar = '';

    public function tanishtir(): string
    {
        return "{$this->ism}, {$this->yosh} yosh, {$this->shahar}dan";
    }
}

$stmt = $pdo->query("SELECT * FROM talabalar");
$talabalar = $stmt->fetchAll(PDO::FETCH_CLASS, Talaba::class);

foreach ($talabalar as $t) {     // har bir $t β€” Talaba obyekti
    echo $t->tanishtir() . "<br>";
}

fetchAll(PDO::FETCH_CLASS, Talaba::class) β€” har bir qatorni Talaba obyektiga aylantiradi. Ustun nomlari (ism, yosh...) class xususiyatlariga mos kelishi kerak. Endi qatorlar oddiy massiv emas, to'liq obyekt β€” ulardan tanishtir() kabi metodlarni chaqira olasiz. Bu β€” bazani obyektga bog'laydigan zamonaviy, toza uslub.