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

PHP

readonly xossalar obyekt yaratilgandan keyin o’zgarmaydi — ma’lumot kutilmaganda buzilmaydi. Ikkita nomlangan konstruktor (yarat, qatordan) niyatni aniq ko’rsatadi.

2. Validatsiya

PHP

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

PHP

5. Ko’rinish

PHP

Ikkinchi maqola sarlavhasidagi hujum urinishi zararsiz matnga aylandi — esc() har chiqishda qo’llanilgani uchun.

6. Nazoratchi

PHP

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

PHP

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

  1. Tahrirlash va o’chirishPUT/DELETE marshrutlari;
  2. Rasm yuklashfayllar darsi qoidalari bilan;
  3. Izohlar — bog’liq jadval va JOIN;
  4. QidiruvLIKE yoki to’liq matnli indeks;
  5. API — o’sha modeldan JSON javob;
  6. 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

  1. Loyihani o’z kompyuteringizda yig’ing va ishga tushiring;
  2. Maqola klassiga tahrirlangan_sana xossasini qo’shing;
  3. validatsiyaga “sarlavha takrorlanmasin” shartini qo’shing;
  4. o’chirish marshrutini CSRF himoyasi bilan yozing;
  5. MaqolaOmbori uchun soxta nusxa yasab, nazoratchini sinang;
  6. sahifalash qo’shing (LIMIT/OFFSET).

Qisqacha xulosa

  • Loyihani qatlamlarga ajrating: model, ombor, nazoratchi, ko’rinish.
  • readonly va nomlangan konstruktor ma’lumotni himoyalaydi.
  • Barcha SQL omborda, har so’rov tayyorlangan.
  • CSRF token har POST formada; hash_equals bilan tekshiring.
  • Chiqishda har doim htmlspecialchars.
  • Kirish xatosi xabari bir xil bo’lsin.
  • Sof va bog’liqliksiz qismlar — eng oson sinaladigani.

Bu dars foydali bo'ldimi?

marta ko'rildi