20-bo‘lim

Amaliy loyiha

Yigirma bo'lim bilimini bitta ishlaydigan ilovada birlashtirish - vazifalar boshqaruvchisi.

🕑 9 daqiqa o‘qish 📄 565 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Loyiha xaritasi
  2. 1-qadam: holat va qiymat obyekti
  3. 2-qadam: istisnolar va tekshiruv
  4. 3-qadam: ombor va xizmat
  5. 4-qadam: HTTP qatlami
  6. Bundan keyin nima
  7. Xulosa

Bu bo'limda haqiqiy ilova quramiz: vazifalar boshqaruvchisi. U ma'lumotlar bazasi bilan ishlaydi, kirishni tekshiradi, xatolarni boshqaradi va JSON API beradi.

Har bir qismda qaysi bo'lim ishlatilganini ko'rsatamiz.

Loyiha xaritasi #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
PHP
$qismlar = [
    ["Holat",        "enum - vazifa holati",            "19 zamonaviy PHP"],
    ["Vazifa",       "readonly qiymat obyekti",         "12 OOP asoslari"],
    ["VazifaXatosi", "ilova istisnolari ierarxiyasi",   "15 xatolar"],
    ["Tekshiruv",    "kirish ma'lumotini tekshirish",   "9 validatsiya"],
    ["Ombor",        "PDO bilan saqlash",               "16 PDO"],
    ["Xizmat",       "biznes mantiq",                   "13 interfeys"],
    ["ApiJavob",     "bir xil shakldagi javob",         "18 JSON va API"],
];

printf("%-14s %-34s %s\n", "QISM", "VAZIFASI", "BO'LIM");
echo str_repeat("-", 72) . "\n";
foreach ($qismlar as [$nom, $vazifa, $bolim]) {
    printf("%-14s %-34s %s\n", $nom, $vazifa, $bolim);
}

echo "\n";
echo "Fayl tuzilmasi (14-bo'lim):\n";
$tuzilma = [
    "public/index.php"          => "yagona kirish nuqtasi",
    "src/Holat.php"             => "App\\Holat",
    "src/Vazifa.php"            => "App\\Vazifa",
    "src/VazifaXatosi.php"      => "App\\VazifaXatosi",
    "src/Ombor.php"             => "App\\Ombor",
    "src/VazifaXizmati.php"     => "App\\VazifaXizmati",
    "config/sozlama.php"        => "sirlar - git ga QO'SHILMAYDI",
    "composer.json"             => "psr-4: App\\ -> src/",
];
foreach ($tuzilma as $yol => $izoh) {
    printf("  %-26s # %s\n", $yol, $izoh);
}
Natija
QISM           VAZIFASI                           BO'LIM
------------------------------------------------------------------------
Holat          enum - vazifa holati               19 zamonaviy PHP
Vazifa         readonly qiymat obyekti            12 OOP asoslari
VazifaXatosi   ilova istisnolari ierarxiyasi      15 xatolar
Tekshiruv      kirish ma'lumotini tekshirish      9 validatsiya
Ombor          PDO bilan saqlash                  16 PDO
Xizmat         biznes mantiq                      13 interfeys
ApiJavob       bir xil shakldagi javob            18 JSON va API

Fayl tuzilmasi (14-bo'lim):
  public/index.php           # yagona kirish nuqtasi
  src/Holat.php              # App\Holat
  src/Vazifa.php             # App\Vazifa
  src/VazifaXatosi.php       # App\VazifaXatosi
  src/Ombor.php              # App\Ombor
  src/VazifaXizmati.php      # App\VazifaXizmati
  config/sozlama.php         # sirlar - git ga QO'SHILMAYDI
  composer.json              # psr-4: App\ -> src/

1-qadam: holat va qiymat obyekti #

PHP
enum Holat: string
{
    case Yangi      = "yangi";
    case Jarayonda  = "jarayonda";
    case Yakunlangan = "yakunlangan";
    case Bekor      = "bekor";

    public function matni(): string
    {
        return match ($this) {
            Holat::Yangi       => "Yangi",
            Holat::Jarayonda   => "Bajarilmoqda",
            Holat::Yakunlangan => "Yakunlangan",
            Holat::Bekor       => "Bekor qilingan",
        };
    }

    public function yopiqmi(): bool
    {
        return $this === Holat::Yakunlangan || $this === Holat::Bekor;
    }

    /** Qaysi holatga o'tish mumkin - biznes qoidasi */
    public function otishMumkinmi(Holat $yangi): bool
    {
        return match ($this) {
            Holat::Yangi      => $yangi !== Holat::Yangi,
            Holat::Jarayonda  => in_array($yangi, [Holat::Yakunlangan, Holat::Bekor], true),
            default           => false,     // yopiq holatdan chiqib bo'lmaydi
        };
    }
}

final readonly class Vazifa
{
    public function __construct(
        public ?int $id,
        public string $sarlavha,
        public Holat $holat,
        public int $muhimlik,
        public ?string $muddat = null,
    ) {}

    public function kechikdimi(string $bugun): bool
    {
        if ($this->muddat === null || $this->holat->yopiqmi()) {
            return false;
        }
        return $this->muddat < $bugun;
    }

    public function holatBilan(Holat $yangi): self
    {
        return new self($this->id, $this->sarlavha, $yangi, $this->muhimlik, $this->muddat);
    }
}

echo "Holat o'tishlari jadvali:\n";

$sarlavha = sprintf("%-14s", "DAN \\ GA");
foreach (Holat::cases() as $h) {
    $sarlavha .= sprintf(" %-12s", $h->value);
}
echo rtrim($sarlavha) . "\n";
echo str_repeat("-", 66) . "\n";

foreach (Holat::cases() as $dan) {
    $qator = sprintf("%-14s", $dan->value);
    foreach (Holat::cases() as $ga) {
        $qator .= sprintf(" %-12s", $dan->otishMumkinmi($ga) ? "mumkin" : "-");
    }
    echo rtrim($qator) . "\n";
}

echo "\n";
$v = new Vazifa(1, "Hisobotni yozish", Holat::Jarayonda, 2, "2024-08-30");
printf("Vazifa:     %s\n", $v->sarlavha);
printf("Holat:      %s\n", $v->holat->matni());
printf("Kechikdimi: %s\n", $v->kechikdimi("2024-09-01") ? "ha" : "yo'q");

$yopiq = $v->holatBilan(Holat::Yakunlangan);
printf("Yakunlangandan keyin kechikdimi: %s\n", $yopiq->kechikdimi("2024-09-01") ? "ha" : "yo'q");
Natija
Holat o'tishlari jadvali:
DAN \ GA       yangi        jarayonda    yakunlangan  bekor
------------------------------------------------------------------
yangi          -            mumkin       mumkin       mumkin
jarayonda      -            -            mumkin       mumkin
yakunlangan    -            -            -            -
bekor          -            -            -            -

Vazifa:     Hisobotni yozish
Holat:      Bajarilmoqda
Kechikdimi: ha
Yakunlangandan keyin kechikdimi: yo'q
Biznes qoidasi enum ichida yashaydi

otishMumkinmi() usuli - bu biznes qoidasi, va u enum ichida turibdi.

Muqobil yechim - bu tekshiruvni kontrollerda yozish bo'lardi. Lekin unda qoida tarqalib ketadi: veb kontroller, API kontroller va konsol buyrug'i - har biri o'z nusxasini yozadi va ular vaqt o'tishi bilan bir-biridan farq qila boshlaydi.

Qoida turning o'zida bo'lsa, uni chetlab o'tish mumkin emas.

Jadvalga e'tibor bering: yopiq holatlardan (yakunlangan, bekor) hech qayerga o'tib bo'lmaydi. Bu ataylab - yakunlangan vazifani qayta ochish uchun alohida amal kerak.

2-qadam: istisnolar va tekshiruv #

PHP
enum Holat: string
{
    case Yangi = "yangi";
    case Jarayonda = "jarayonda";
    case Yakunlangan = "yakunlangan";
    case Bekor = "bekor";
}

abstract class VazifaXatosi extends RuntimeException
{
    abstract public function kod(): int;
    abstract public function foydalanuvchiXabari(): string;
}

final class VazifaTopilmadi extends VazifaXatosi
{
    public function __construct(public readonly int $id)
    {
        parent::__construct("Vazifa topilmadi: id={$id}");
    }

    public function kod(): int { return 404; }
    public function foydalanuvchiXabari(): string { return "Bunday vazifa mavjud emas."; }
}

final class MalumotNotogri extends VazifaXatosi
{
    public function __construct(public readonly array $maydonlar)
    {
        parent::__construct("Validatsiya xatosi: " . implode(", ", array_keys($maydonlar)));
    }

    public function kod(): int { return 422; }
    public function foydalanuvchiXabari(): string { return "Kiritilgan ma'lumot noto'g'ri."; }
}

final class OtishTaqiqlangan extends VazifaXatosi
{
    public function __construct(public readonly string $dan, public readonly string $ga)
    {
        parent::__construct("Holat o'tishi taqiqlangan: {$dan} -> {$ga}");
    }

    public function kod(): int { return 409; }
    public function foydalanuvchiXabari(): string
    {
        return "Bu holatga o'tish mumkin emas.";
    }
}

/** Kiruvchi ma'lumotni tekshiradi va TOZA massiv qaytaradi */
function vazifaniTekshir(array $kirish): array
{
    $xatolar = [];

    $sarlavha = trim((string) ($kirish["sarlavha"] ?? ""));
    if (mb_strlen($sarlavha) < 3 || mb_strlen($sarlavha) > 200) {
        $xatolar["sarlavha"] = "Sarlavha 3-200 belgi bo'lsin.";
    }

    $muhimlik = filter_var($kirish["muhimlik"] ?? null, FILTER_VALIDATE_INT, [
        "options" => ["min_range" => 1, "max_range" => 5],
    ]);
    if ($muhimlik === false) {
        $xatolar["muhimlik"] = "Muhimlik 1 dan 5 gacha bo'lsin.";
    }

    $holat = Holat::tryFrom((string) ($kirish["holat"] ?? "yangi"));
    if ($holat === null) {
        $xatolar["holat"] = "Noma'lum holat.";
    }

    $muddat = $kirish["muddat"] ?? null;
    if ($muddat !== null && $muddat !== "") {
        $d = DateTimeImmutable::createFromFormat("Y-m-d", (string) $muddat);
        $x = DateTimeImmutable::getLastErrors();
        if ($d === false || ($x !== false && ($x["warning_count"] + $x["error_count"]) > 0)) {
            $xatolar["muddat"] = "Sana YYYY-MM-DD shaklida bo'lsin.";
        }
    } else {
        $muddat = null;
    }

    if ($xatolar !== []) {
        throw new MalumotNotogri($xatolar);
    }

    return ["sarlavha" => $sarlavha, "muhimlik" => (int) $muhimlik,
            "holat" => $holat, "muddat" => $muddat];
}

$sinovlar = [
    "to'g'ri"        => ["sarlavha" => "Hisobotni yozish", "muhimlik" => "3", "muddat" => "2024-09-30"],
    "qisqa sarlavha" => ["sarlavha" => "ab", "muhimlik" => "3"],
    "yomon muhimlik" => ["sarlavha" => "Normal sarlavha", "muhimlik" => "99"],
    "yomon sana"     => ["sarlavha" => "Normal sarlavha", "muhimlik" => "2", "muddat" => "2024-02-31"],
    "hammasi yomon"  => ["sarlavha" => "", "muhimlik" => "abc", "holat" => "yoq"],
];

foreach ($sinovlar as $nom => $kirish) {
    try {
        $toza = vazifaniTekshir($kirish);
        printf("%-16s OK -> %s (muhimlik %d)\n", $nom, $toza["sarlavha"], $toza["muhimlik"]);
    } catch (MalumotNotogri $x) {
        printf("%-16s HTTP %d\n", $nom, $x->kod());
        foreach ($x->maydonlar as $maydon => $xabar) {
            printf("%-16s   %-10s %s\n", "", $maydon, $xabar);
        }
    }
}
Natija
to'g'ri          OK -> Hisobotni yozish (muhimlik 3)
qisqa sarlavha   HTTP 422
                   sarlavha   Sarlavha 3-200 belgi bo'lsin.
yomon muhimlik   HTTP 422
                   muhimlik   Muhimlik 1 dan 5 gacha bo'lsin.
yomon sana       HTTP 422
                   muddat     Sana YYYY-MM-DD shaklida bo'lsin.
hammasi yomon    HTTP 422
                   sarlavha   Sarlavha 3-200 belgi bo'lsin.
                   muhimlik   Muhimlik 1 dan 5 gacha bo'lsin.
                   holat      Noma'lum holat.
So'rov qatlamlar bo'ylab 1. KIRISH NUQTASI public/index.php marshrutlash, global ishlovchi 8, 15-bo'limlar 2. TEKSHIRUV vazifaniTekshir() noto'g'ri bo'lsa - 422 va to'xtash 9-bo'lim 3. BIZNES MANTIQ VazifaXizmati qoidalar, holat o'tishlari 12, 13, 19-bo'limlar 4. SAQLASH Ombor (PDO) tayyorlangan so'rovlar 16-bo'lim 5. JAVOB ApiJavob JSON yoki HTML, to'g'ri holat kodi 17, 18-bo'limlar Har bir qatlam faqat pastdagisini biladi Biznes mantiq HTTP haqida hech narsa bilmaydi - shuning uchun uni sinash oson.
Qatlamlar ajratilgan - har birini alohida sinash va almashtirish mumkin

3-qadam: ombor va xizmat #

PHP
enum Holat: string
{
    case Yangi = "yangi";
    case Jarayonda = "jarayonda";
    case Yakunlangan = "yakunlangan";
    case Bekor = "bekor";

    public function otishMumkinmi(Holat $yangi): bool
    {
        return match ($this) {
            Holat::Yangi     => $yangi !== Holat::Yangi,
            Holat::Jarayonda => in_array($yangi, [Holat::Yakunlangan, Holat::Bekor], true),
            default          => false,
        };
    }
}

final readonly class Vazifa
{
    public function __construct(
        public ?int $id,
        public string $sarlavha,
        public Holat $holat,
        public int $muhimlik,
    ) {}
}

final class VazifaTopilmadi extends RuntimeException {}
final class OtishTaqiqlangan extends RuntimeException {}

/** Saqlash qatlami - FAQAT ma'lumot bilan ishlaydi */
final class Ombor
{
    public function __construct(private readonly PDO $pdo)
    {
        $this->pdo->exec("
            CREATE TABLE vazifalar (
                id       INTEGER PRIMARY KEY AUTOINCREMENT,
                sarlavha TEXT    NOT NULL,
                holat    TEXT    NOT NULL,
                muhimlik INTEGER NOT NULL
            )
        ");
    }

    public function qosh(Vazifa $v): Vazifa
    {
        $stmt = $this->pdo->prepare(
            "INSERT INTO vazifalar (sarlavha, holat, muhimlik) VALUES (?, ?, ?)"
        );
        $stmt->execute([$v->sarlavha, $v->holat->value, $v->muhimlik]);

        return new Vazifa((int) $this->pdo->lastInsertId(), $v->sarlavha, $v->holat, $v->muhimlik);
    }

    public function top(int $id): Vazifa
    {
        $stmt = $this->pdo->prepare("SELECT * FROM vazifalar WHERE id = ?");
        $stmt->execute([$id]);
        $q = $stmt->fetch();

        if ($q === false) {
            throw new VazifaTopilmadi("Vazifa topilmadi: {$id}");
        }
        return $this->qatordan($q);
    }

    /** @return list<Vazifa> */
    public function royxat(?Holat $holat = null): array
    {
        if ($holat === null) {
            $stmt = $this->pdo->query("SELECT * FROM vazifalar ORDER BY muhimlik DESC, id");
        } else {
            $stmt = $this->pdo->prepare(
                "SELECT * FROM vazifalar WHERE holat = ? ORDER BY muhimlik DESC, id"
            );
            $stmt->execute([$holat->value]);
        }

        // array_values - 5 va 18-bo'limlardagi qoida
        return array_values(array_map($this->qatordan(...), $stmt->fetchAll()));
    }

    public function holatniYangila(int $id, Holat $yangi): Vazifa
    {
        $stmt = $this->pdo->prepare("UPDATE vazifalar SET holat = ? WHERE id = ?");
        $stmt->execute([$yangi->value, $id]);
        return $this->top($id);
    }

    private function qatordan(array $q): Vazifa
    {
        return new Vazifa(
            (int) $q["id"],
            (string) $q["sarlavha"],
            Holat::from((string) $q["holat"]),
            (int) $q["muhimlik"],
        );
    }
}

/** Biznes qatlam - qoidalarni biladi, HTTP ni bilmaydi */
final class VazifaXizmati
{
    public function __construct(private readonly Ombor $ombor) {}

    public function yarat(string $sarlavha, int $muhimlik): Vazifa
    {
        return $this->ombor->qosh(new Vazifa(null, $sarlavha, Holat::Yangi, $muhimlik));
    }

    public function holatniOzgartir(int $id, Holat $yangi): Vazifa
    {
        $v = $this->ombor->top($id);

        if (!$v->holat->otishMumkinmi($yangi)) {
            throw new OtishTaqiqlangan(
                "Holat o'tishi taqiqlangan: {$v->holat->value} -> {$yangi->value}"
            );
        }
        return $this->ombor->holatniYangila($id, $yangi);
    }
}

// --- Ishlatish ---
$pdo = new PDO("sqlite::memory:", null, null, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$xizmat = new VazifaXizmati($ombor = new Ombor($pdo));

$xizmat->yarat("Hisobotni yozish", 3);
$xizmat->yarat("Serverni yangilash", 5);
$xizmat->yarat("Xatga javob berish", 1);

echo "Barcha vazifalar:\n";
foreach ($ombor->royxat() as $v) {
    printf("  #%d [%d] %-22s %s\n", $v->id, $v->muhimlik, $v->sarlavha, $v->holat->value);
}

echo "\nHolatni o'zgartiramiz:\n";
$xizmat->holatniOzgartir(1, Holat::Jarayonda);
$xizmat->holatniOzgartir(1, Holat::Yakunlangan);
echo "  #1 yakunlandi\n";

echo "\nTaqiqlangan o'tishga urinish:\n";
try {
    $xizmat->holatniOzgartir(1, Holat::Jarayonda);
} catch (OtishTaqiqlangan $x) {
    echo "  " . $x->getMessage() . "\n";
}

echo "\nMavjud bo'lmagan vazifa:\n";
try {
    $xizmat->holatniOzgartir(999, Holat::Bekor);
} catch (VazifaTopilmadi $x) {
    echo "  " . $x->getMessage() . "\n";
}

echo "\nFaqat yangi vazifalar:\n";
foreach ($ombor->royxat(Holat::Yangi) as $v) {
    printf("  #%d %s\n", $v->id, $v->sarlavha);
}
Natija
Barcha vazifalar:
  #2 [5] Serverni yangilash     yangi
  #1 [3] Hisobotni yozish       yangi
  #3 [1] Xatga javob berish     yangi

Holatni o'zgartiramiz:
  #1 yakunlandi

Taqiqlangan o'tishga urinish:
  Holat o'tishi taqiqlangan: yakunlangan -> jarayonda

Mavjud bo'lmagan vazifa:
  Vazifa topilmadi: 999

Faqat yangi vazifalar:
  #2 Serverni yangilash
  #3 Xatga javob berish
Ombor va Xizmat nima uchun alohida

Ombor faqat saqlash bilan shug'ullanadi: SQL, qatorlarni obyektga aylantirish. U biznes qoidalarini bilmaydi.

VazifaXizmati esa qoidalarni biladi, lekin SQL yozmaydi.

Bu ajratish uch foyda beradi:

FoydaIzoh
Sinash osonOmbor o'rniga soxta obyekt qo'yish mumkin
Baza almashtiriladiSQLite, MySQL, fayl - Xizmat o'zgarmaydi
Qoida bir joydaUch kontroller bir xil Xizmat ni chaqiradi

Ombor ni interfeys qilib, VazifaXizmati ni unga bog'lash yanada yaxshiroq bo'lardi (13-bo'lim):

PHP
interface OmborInterfeysi {
    public function top(int $id): Vazifa;
    public function qosh(Vazifa $v): Vazifa;
}

Shunda sinovlarda haqiqiy bazasiz ishlash mumkin.

E'tibor bering, royxat() da array_values ishlatilgan - 5 va 18-bo'limlardagi qoida. Usiz JSON javobi massiv emas, obyekt bo'lib chiqardi.

4-qadam: HTTP qatlami #

PHP
<?php
// public/index.php - yagona kirish nuqtasi
declare(strict_types=1);

require __DIR__ . "/../vendor/autoload.php";

use App\{Holat, VazifaXizmati, VazifaXatosi};

// 15-bo'lim: global ishlovchi
set_exception_handler(function (Throwable $x): void {
    error_log(sprintf("[%s] %s: %s in %s:%d",
        date("c"), $x::class, $x->getMessage(), $x->getFile(), $x->getLine()));

    $kod = $x instanceof VazifaXatosi ? $x->kod() : 500;
    $xabar = $x instanceof VazifaXatosi
        ? $x->foydalanuvchiXabari()
        : "Ichki xatolik yuz berdi.";

    http_response_code($kod);
    header("Content-Type: application/json; charset=utf-8");
    echo json_encode(["ok" => false, "error" => ["xabar" => $xabar]],
        JSON_UNESCAPED_UNICODE);
    exit(1);
});

$sozlama = require __DIR__ . "/../config/sozlama.php";
$pdo = new PDO($sozlama["dsn"], $sozlama["user"], $sozlama["parol"], [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
]);

$xizmat = new VazifaXizmati(new Ombor($pdo));

// 8 va 18-bo'limlar: so'rovni o'qish
$usul = $_SERVER["REQUEST_METHOD"];
$yol  = trim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) ?? "/", "/");
$qism = $yol === "" ? [] : explode("/", $yol);

$tana = [];
if (in_array($usul, ["POST", "PATCH"], true)) {
    $xom = file_get_contents("php://input");      // $_POST JSON ni O'QIMAYDI
    $tana = $xom === "" ? [] : json_decode($xom, true, 512, JSON_THROW_ON_ERROR);
}

// 6 va 19-bo'limlar: match bilan marshrutlash
[$kod, $javob] = match (true) {
    $usul === "GET" && $qism === ["api", "vazifalar"]
        => [200, $xizmat->royxat(Holat::tryFrom($_GET["holat"] ?? ""))],

    $usul === "POST" && $qism === ["api", "vazifalar"]
        => [201, $xizmat->yarat(
                 (string) ($tana["sarlavha"] ?? ""),
                 (int) ($tana["muhimlik"] ?? 3)
           )],

    $usul === "PATCH" && count($qism) === 3 && $qism[1] === "vazifalar"
        => [200, $xizmat->holatniOzgartir(
                 (int) $qism[2],
                 Holat::from((string) ($tana["holat"] ?? ""))
           )],

    default => throw new RuntimeException("Bunday manzil yo'q"),
};

http_response_code($kod);
header("Content-Type: application/json; charset=utf-8");
header("X-Content-Type-Options: nosniff");     // 17-bo'lim
echo json_encode(["ok" => true, "data" => $javob],
    JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
Kontroller qanchalik yupqa ekaniga e'tibor bering

Yuqoridagi faylda birorta biznes qoidasi yo'q. U faqat uch ish qiladi:

  1. So'rovni o'qib, kerakli turlarga aylantiradi.
  2. Tegishli xizmat usulini chaqiradi.
  3. Natijani JSON ga o'rab qaytaradi.

Barcha qoidalar - holat o'tishlari, validatsiya, saqlash - pastdagi qatlamlarda.

Bu nima beradi: ertaga konsol buyrug'i yoki Telegram bot qo'shsangiz, ular xuddi shu VazifaXizmati ni chaqiradi. Qoidalar takrorlanmaydi.

Va e'tibor bering, try/catch ham yo'q - global ishlovchi barcha VazifaXatosi larni tutib, to'g'ri HTTP kodini o'zi qo'yadi. Har bir istisno o'z kodini biladi (kod() usuli).

Bundan keyin nima #

PHP
$yol = [
    ["Sinovlar", "PHPUnit yoki Pest", "Har bir qatlamni alohida sinash"],
    ["Statik tahlil", "PHPStan yoki Psalm", "Xatolarni ishga tushirmasdan topish"],
    ["Kod uslubi", "PHP-CS-Fixer", "Jamoada bir xil formatlash"],
    ["Freymvork", "Laravel yoki Symfony", "Marshrutlash, ORM, DI konteyner"],
    ["Shablon", "Twig", "HTML ni PHP dan ajratish"],
    ["Jurnal", "Monolog", "Tuzilgan jurnal yozish"],
    ["Kesh", "Redis yoki APCu", "Tez-tez so'raladigan ma'lumot"],
    ["Navbat", "RabbitMQ yoki baza", "Uzoq amallarni fon rejimiga o'tkazish"],
];

printf("%-16s %-24s %s\n", "MAVZU", "VOSITA", "NIMA BERADI");
echo str_repeat("-", 76) . "\n";
foreach ($yol as [$mavzu, $vosita, $nima]) {
    printf("%-16s %-24s %s\n", $mavzu, $vosita, $nima);
}

echo "\n";
echo "Loyihani kengaytirish g'oyalari:\n";
$goyalar = [
    "Foydalanuvchi va kirish tizimi (10, 17-bo'limlar)",
    "Vazifalarni foydalanuvchiga biriktirish",
    "Izohlar va fayl biriktirish (11-bo'lim)",
    "Muddat yaqinlashganda elektron xat yuborish",
    "CSV eksport (11-bo'lim)",
    "Har bir qatlam uchun birlik sinovlari",
];
foreach ($goyalar as $i => $g) {
    printf("  %d. %s\n", $i + 1, $g);
}

echo "\n";
echo "Eng muhim maslahat:\n";
echo "  Kichik loyiha yozing va uni OXIRIGACHA yetkazing.\n";
echo "  Tugatilgan kichik loyiha - tugatilmagan kattadan qimmatliroq.\n";
Natija
MAVZU            VOSITA                   NIMA BERADI
----------------------------------------------------------------------------
Sinovlar         PHPUnit yoki Pest        Har bir qatlamni alohida sinash
Statik tahlil    PHPStan yoki Psalm       Xatolarni ishga tushirmasdan topish
Kod uslubi       PHP-CS-Fixer             Jamoada bir xil formatlash
Freymvork        Laravel yoki Symfony     Marshrutlash, ORM, DI konteyner
Shablon          Twig                     HTML ni PHP dan ajratish
Jurnal           Monolog                  Tuzilgan jurnal yozish
Kesh             Redis yoki APCu          Tez-tez so'raladigan ma'lumot
Navbat           RabbitMQ yoki baza       Uzoq amallarni fon rejimiga o'tkazish

Loyihani kengaytirish g'oyalari:
  1. Foydalanuvchi va kirish tizimi (10, 17-bo'limlar)
  2. Vazifalarni foydalanuvchiga biriktirish
  3. Izohlar va fayl biriktirish (11-bo'lim)
  4. Muddat yaqinlashganda elektron xat yuborish
  5. CSV eksport (11-bo'lim)
  6. Har bir qatlam uchun birlik sinovlari

Eng muhim maslahat:
  Kichik loyiha yozing va uni OXIRIGACHA yetkazing.
  Tugatilgan kichik loyiha - tugatilmagan kattadan qimmatliroq.
Yakuniy topshiriq
  1. Holat enum ini yozing va o'tish qoidasini qo'shing.
  2. Vazifa ni readonly qiymat obyekti sifatida yozing.
  3. Istisno ierarxiyasini quring - har biri o'z HTTP kodini bilsin.
  4. Validatsiya funksiyasini yozing va besh xil kirish bilan sinang.
  5. Ombor sinfini PDO bilan yozing.
  6. VazifaXizmati ni yozing - u SQL bilmasin.
  7. Taqiqlangan holat o'tishini sinab ko'ring.
  8. Ombor uchun interfeys ajrating va soxta amalga oshirish yozing.
  9. public/index.php kirish nuqtasini yozing.
  10. Loyihani - .env va config/ veb ildizidan tashqarida ekanini tekshiring.

Xulosa #

  • Haqiqiy ilova - alohida imkoniyatlar emas, ularning birgalikda ishlashi.
  • Biznes qoidasini turning o'zida saqlang - shunda uni chetlab o'tib bo'lmaydi.
  • Qiymat obyektlarini readonly qiling; "o'zgartirish" yangi obyekt qaytarsin.
  • Istisno o'z HTTP kodini bilsin - kontrollerda if zanjiri kerak bo'lmaydi.
  • Validatsiya toza va turlangan ma'lumot qaytarsin, xom massiv emas.
  • Ombor faqat saqlaydi, Xizmat faqat qoidalarni biladi.
  • Biznes qatlam HTTP haqida hech narsa bilmasin - shunda uni sinash oson.
  • Kontroller yupqa bo'lsin: o'qish, chaqirish, javob berish.
  • Ro'yxat qaytarayotganda array_values() ni unutmang.
  • Global istisno ishlovchisi barcha try/catch ni kontrollerdan olib tashlaydi.
  • Keyingi qadam - sinovlar va statik tahlil: ular kodni ishonchli qiladi.

Darslik shu bilan yakunlandi. Endi eng muhim qadam sizniki: o'z loyihangizni yozing va uni oxirigacha yetkazing.

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.