PHP yakuniy loyiha: MVC blog
Bu darsda O’rta daraja bo’ylab o’rganilganlarni bitta loyihada birlashtiramiz: maqolalar blogi — ro’yxat, ko’rish, qo’shish, autentifikatsiya va himoya.
Loyiha tuzilishi
blog/
├── public/
│ ├── index.php ← yagona kirish nuqtasi
│ └── .htaccess
├── src/
│ ├── Router.php
│ ├── Model/
│ │ ├── Maqola.php ← ma'lumot obyekti
│ │ └── MaqolaOmbori.php ← baza bilan ishlash
│ ├── Nazoratchi/
│ │ ├── MaqolaNazoratchisi.php
│ │ └── KirishNazoratchisi.php
│ └── Yordamchi/
│ ├── Korinish.php
│ └── Csrf.php
├── korinishlar/
├── tests/
├── .env
└── composer.json
Har papka bitta mas’uliyat: model — ma’lumot, nazoratchi — qaror, ko’rinish — HTML.
1. Ma’lumot obyekti
readonly xossalar obyekt yaratilgandan keyin o’zgarmaydi — ma’lumot
kutilmaganda buzilmaydi. Ikkita nomlangan konstruktor (yarat,
qatordan) niyatni aniq ko’rsatadi.
2. Validatsiya
Tekshiruv alohida klassda — uni nazoratchidan ham, API’dan ham, testdan ham chaqirish mumkin.
3. Ombor (baza qatlami)
<?php
namespace App\Model;
use PDO;
final class MaqolaOmbori
{
public function __construct(private PDO $pdo) {}
public function hammasi(int $limit = 10, int $offset = 0): array
{
$s = $this->pdo->prepare(
"SELECT * FROM maqolalar ORDER BY sana DESC LIMIT :limit OFFSET :offset"
);
$s->bindValue(":limit", $limit, PDO::PARAM_INT);
$s->bindValue(":offset", $offset, PDO::PARAM_INT);
$s->execute();
return array_map(
fn(array $q) => Maqola::qatordan($q),
$s->fetchAll()
);
}
public function slugBoyicha(string $slug): ?Maqola
{
$s = $this->pdo->prepare("SELECT * FROM maqolalar WHERE slug = ?");
$s->execute([$slug]);
$q = $s->fetch();
return $q === false ? null : Maqola::qatordan($q);
}
public function saqla(Maqola $m): int
{
$s = $this->pdo->prepare(
"INSERT INTO maqolalar (sarlavha, matn, slug, sana)
VALUES (:sarlavha, :matn, :slug, :sana)"
);
$s->execute([
"sarlavha" => $m->sarlavha,
"matn" => $m->matn,
"slug" => $m->slug,
"sana" => $m->sana,
]);
return (int) $this->pdo->lastInsertId();
}
public function soni(): int
{
return (int) $this->pdo->query("SELECT COUNT(*) FROM maqolalar")->fetchColumn();
}
}
Barcha SQL bitta joyda, har so’rov tayyorlangan — injection mumkin emas.
4. CSRF himoyasi
5. Ko’rinish
Ikkinchi maqola sarlavhasidagi hujum urinishi zararsiz matnga aylandi —
esc() har chiqishda qo’llanilgani uchun.
6. Nazoratchi
Nazoratchi HTML chizmaydi — u holat va ma’lumot qaytaradi. Shuning uchun uni sinash oson.
7. Kirish nazorati
<?php
final class KirishNazoratchisi
{
public function __construct(
private PDO $pdo,
private Csrf $csrf,
) {}
public function kir(array $post): array
{
if (!$this->csrf->tekshir($post["csrf"] ?? null)) {
return ["ok" => false, "xato" => "So'rov rad etildi"];
}
$login = trim($post["login"] ?? "");
$parol = $post["parol"] ?? "";
$s = $this->pdo->prepare(
"SELECT id, parol_hash FROM foydalanuvchilar WHERE login = ?"
);
$s->execute([$login]);
$f = $s->fetch();
// Bir xil xabar: login bormi-yo'qmi — bildirmaymiz
if ($f === false || !password_verify($parol, $f["parol_hash"])) {
return ["ok" => false, "xato" => "Login yoki parol xato"];
}
session_regenerate_id(true);
$_SESSION["foydalanuvchi_id"] = (int) $f["id"];
return ["ok" => true];
}
public function chiq(): void
{
$_SESSION = [];
session_destroy();
}
}
8. Kirish nuqtasi
<?php
declare(strict_types=1);
require __DIR__ . "/../vendor/autoload.php";
use App\Router;
use App\Model\MaqolaOmbori;
use App\Nazoratchi\MaqolaNazoratchisi;
use App\Yordamchi\Csrf;
// --- Sozlama ---
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
$dotenv->load();
$debug = filter_var($_ENV["APP_DEBUG"] ?? "false", FILTER_VALIDATE_BOOL);
ini_set("display_errors", $debug ? "1" : "0");
ini_set("log_errors", "1");
error_reporting(E_ALL);
// --- Sessiya ---
ini_set("session.cookie_httponly", "1");
ini_set("session.cookie_samesite", "Lax");
ini_set("session.use_strict_mode", "1");
session_start();
// --- Baza ---
$pdo = new PDO(
"mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']};charset=utf8mb4",
$_ENV["DB_USER"],
$_ENV["DB_PAROL"],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
// --- Bog'liqliklar ---
$csrf = new Csrf($_SESSION);
$nazoratchi = new MaqolaNazoratchisi(new MaqolaOmbori($pdo), $csrf);
// --- Marshrutlar ---
$router = new Router();
$router->get("/", [$nazoratchi, "royxat"]);
$router->get("/maqola/{slug}", [$nazoratchi, "bittasi"]);
$router->get("/yangi", [$nazoratchi, "forma"]);
$router->post("/yangi", [$nazoratchi, "saqla"]);
// --- Ishga tushirish ---
try {
echo $router->ishla(
$_SERVER["REQUEST_METHOD"],
parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH)
);
} catch (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
echo $debug ? $e->getMessage() : "Xatolik yuz berdi";
}
9. Testlar
Validatsiya bazadan mustaqil bo’lgani uchun test tez va bashorat qilinadigan.
Qanday naqshlar ishlatildi
| Naqsh | Dars |
|---|---|
Klass, readonly, nomlangan konstruktor |
Klass va obyekt |
| Interfeys, bog’liqlikni kiritish | Meros va interfeys |
Sessiya, password_verify |
Sessiya va cookie |
| PDO, tayyorlangan so’rov | PDO |
CSRF, hash_equals, XSS himoyasi |
Xavfsizlik |
| Router, layout, qatlamlar | Shablon va MVC |
Composer, autoload, .env |
Composer |
| Birlik testlari | Testlash |
| Muhit sozlamalari | Deploy |
Keyingi qadamlar
- Tahrirlash va o’chirish —
PUT/DELETEmarshrutlari; - Rasm yuklash — fayllar darsi qoidalari bilan;
- Izohlar — bog’liq jadval va
JOIN; - Qidiruv —
LIKEyoki to’liq matnli indeks; - API — o’sha modeldan JSON javob;
- Keshlash — tez-tez so’raladigan sahifalarni saqlash.
Bo’lim yakuni
O’rta daraja tugadi. Siz o’rgandingiz:
- OOP — klass, meros, interfeys, polimorfizm;
- Holat — sessiya, cookie, autentifikatsiya;
- Ma’lumot — fayllar, PDO, JSON va API;
- Tuzilma — Composer, namespace, MVC, router;
- Sifat — xavfsizlik, testlar, deploy.
Amaliy mashq
- Loyihani o’z kompyuteringizda yig’ing va ishga tushiring;
Maqolaklassigatahrirlangan_sanaxossasini qo’shing;- validatsiyaga “sarlavha takrorlanmasin” shartini qo’shing;
- o’chirish marshrutini CSRF himoyasi bilan yozing;
MaqolaOmboriuchun soxta nusxa yasab, nazoratchini sinang;- sahifalash qo’shing (
LIMIT/OFFSET).
Qisqacha xulosa
- Loyihani qatlamlarga ajrating: model, ombor, nazoratchi, ko’rinish.
readonlyva nomlangan konstruktor ma’lumotni himoyalaydi.- Barcha SQL omborda, har so’rov tayyorlangan.
- CSRF token har
POSTformada;hash_equalsbilan tekshiring. - Chiqishda har doim
htmlspecialchars. - Kirish xatosi xabari bir xil bo’lsin.
- Sof va bog’liqliksiz qismlar — eng oson sinaladigani.