REST API — JSON qaytaruvchi oddiy API

~30 daq · PHP · Zamonaviy PHP

Muammodan boshlaylik

Todo ilovangiz yaxshi ishlayapti. Endi:

  • Mobil dasturchi Flutter ilova yasamoqchi
  • Frontend dasturchi React bilan yangi interfeys yozmoqchi
  • Telegram bot vazifalarni eslatib turishi kerak

Ularning hammasiga HTML kerak emas. Ularga ma'lumot kerak — JSON ko'rinishida, va uni qanday so'rashni aniq biladigan qoidalar. Bu qoidalar to'plami — API. Eng keng tarqalgan uslubi — REST.


REST qoidalari

REST da hamma narsa resurs (vazifa, foydalanuvchi, buyurtma) va har bir resurs bilan HTTP metodlari orqali ishlanadi:

Metod URL Ma'nosi Javob kodi
GET /api/vazifalar Ro'yxat 200
GET /api/vazifalar/5 Bittasini olish 200 / 404
POST /api/vazifalar Yangi yaratish 201
PUT / PATCH /api/vazifalar/5 O'zgartirish 200
DELETE /api/vazifalar/5 O'chirish 204

Qoidalar:

  • URL — ot (/vazifalar), fe'l emas (/vazifaniOchir ❌). Amal — metod orqali
  • Ko'plikda: /vazifalar, /vazifalar/5
  • Natija — status kod orqali bildiriladi, 200 ichida {"xato": true} emas

PUT — resursni butunlay almashtirish, PATCH — faqat berilgan maydonlarni o'zgartirish. Amalda ko'pincha PATCH qulayroq.


API yordamchilari

src/Api.php:

<?php
declare(strict_types=1);

namespace App;

final class Api
{
    public static function javob(mixed $data, int $status = 200): never
    {
        http_response_code($status);
        header('Content-Type: application/json; charset=utf-8');
        if ($status !== 204) {
            echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
        }
        exit;
    }

    public static function xato(string $xabar, int $status, array $tafsilot = []): never
    {
        self::javob(['xato' => $xabar] + ($tafsilot ? ['tafsilot' => $tafsilot] : []), $status);
    }

    public static function tana(): array
    {
        $xom = file_get_contents('php://input');
        if ($xom === '' || $xom === false) {
            return [];
        }
        try {
            $data = json_decode($xom, true, flags: JSON_THROW_ON_ERROR);
        } catch (\JsonException) {
            self::xato("JSON noto'g'ri", 400);
        }
        return is_array($data) ? $data : self::xato("JSON ob'ekt kutilgan", 400);
    }
}

API controller

src/Controller/Api/VazifaApiController.php:

<?php
declare(strict_types=1);

namespace App\Controller\Api;

use App\Api;
use App\Model\VazifaRepository;

final class VazifaApiController
{
    private VazifaRepository $repo;

    public function __construct()
    {
        $this->repo = new VazifaRepository();
    }

    // GET /api/vazifalar
    public function royxat(): void
    {
        $vazifalar = array_map([$this, 'formatla'], $this->repo->hammasi());
        Api::javob(['data' => $vazifalar]);
    }

    // GET /api/vazifalar/{id}
    public function bitta(string $id): void
    {
        $v = $this->repo->top((int) $id) ?? Api::xato('Vazifa topilmadi', 404);
        Api::javob(['data' => $this->formatla($v)]);
    }

    // POST /api/vazifalar
    public function yaratish(): void
    {
        $tana = Api::tana();
        $matn = trim((string) ($tana['matn'] ?? ''));

        if ($xatolar = VazifaRepository::tekshir($matn)) {
            Api::xato('Validatsiya xatosi', 422, ['matn' => $xatolar]);
        }

        $id = $this->repo->qosh($matn);
        header("Location: /api/vazifalar/$id");
        Api::javob(['data' => $this->formatla($this->repo->top($id))], 201);
    }

    // PATCH /api/vazifalar/{id}
    public function yangilash(string $id): void
    {
        $v = $this->repo->top((int) $id) ?? Api::xato('Vazifa topilmadi', 404);
        $tana = Api::tana();

        if (array_key_exists('matn', $tana)) {
            $matn = trim((string) $tana['matn']);
            if ($xatolar = VazifaRepository::tekshir($matn)) {
                Api::xato('Validatsiya xatosi', 422, ['matn' => $xatolar]);
            }
            $this->repo->yangila((int) $id, $matn);
        }

        Api::javob(['data' => $this->formatla($this->repo->top((int) $id))]);
    }

    // DELETE /api/vazifalar/{id}
    public function ochirish(string $id): void
    {
        $this->repo->top((int) $id) ?? Api::xato('Vazifa topilmadi', 404);
        $this->repo->ochir((int) $id);
        Api::javob(null, 204);
    }

    /** Bazadagi qatorni API formatiga o'girish */
    private function formatla(array $v): array
    {
        return [
            'id'         => (int) $v['id'],
            'matn'       => $v['matn'],
            'bajarildi'  => (bool) $v['bajarildi'],
            'yaratilgan' => (new \DateTimeImmutable($v['yaratilgan']))->format(DATE_ATOM),
        ];
    }
}

formatla() muhim: bazadagi ustunlar to'g'ridan-to'g'ri tashqariga chiqmaydi. bajarildi — 0/1 emas, true/false. Sana — ISO 8601 formatida. Ertaga bazada ustun nomini o'zgartirsangiz — API mijozlari buzilmaydi.


Yo'llar

Routerga put, patch, delete metodlarini qo'shamiz (41-darsdagi get/post kabi), keyin:

<?php

use App\Controller\Api\VazifaApiController as VA;

$router->get('/api/vazifalar',          [VA::class, 'royxat']);
$router->post('/api/vazifalar',         [VA::class, 'yaratish']);
$router->get('/api/vazifalar/{id}',     [VA::class, 'bitta']);
$router->patch('/api/vazifalar/{id}',   [VA::class, 'yangilash']);
$router->delete('/api/vazifalar/{id}',  [VA::class, 'ochirish']);

Sinab ko'rish

Brauzer faqat GET yuboradi. Qolganlari uchun curl (terminal), Postman yoki VS Code dagi REST Client kengaytmasi:

# Ro'yxat
curl http://localhost:8000/api/vazifalar

# Yaratish
curl -X POST http://localhost:8000/api/vazifalar \
     -H "Content-Type: application/json" \
     -d '{"matn": "Non olish"}'

# O'zgartirish
curl -X PATCH http://localhost:8000/api/vazifalar/1 \
     -H "Content-Type: application/json" \
     -d '{"matn": "Non va sut olish"}'

# O'chirish
curl -X DELETE http://localhost:8000/api/vazifalar/1 -i

Javoblar:

// 201 Created
{"data": {"id": 7, "matn": "Non olish", "bajarildi": false, "yaratilgan": "2026-11-02T14:30:00+05:00"}}

// 422 Unprocessable Content
{"xato": "Validatsiya xatosi", "tafsilot": {"matn": ["Vazifa matni 1-255 belgi bo'lsin"]}}

// 404 Not Found
{"xato": "Vazifa topilmadi"}

Autentifikatsiya — qisqacha

API da sessiya cookie kamdan-kam ishlatiladi (mobil ilovalarda noqulay). Odatda token:

curl http://localhost:8000/api/vazifalar -H "Authorization: Bearer 7f3a9c..."
<?php

$sarlavha = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!preg_match('/^Bearer\s+(\S+)$/', $sarlavha, $m)) {
    Api::xato('Token kerak', 401);
}

$stmt = db()->prepare('SELECT user_id FROM api_tokenlar WHERE token_hash = ?');
$stmt->execute([hash('sha256', $m[1])]);
$userId = $stmt->fetchColumn() ?: Api::xato("Token noto'g'ri", 401);

Token ham parol kabi — bazada xesh sifatida saqlanadi. (Bu yerda sha256 yetarli, chunki token uzun va tasodifiy — parol kabi taxmin qilib bo'lmaydi.) Laravel da buni Sanctum paketi tayyor qilib beradi.


CORS

Boshqa domendagi frontend (http://localhost:5173 dagi React) API ga so'rov yuborsa, brauzer uni bloklaydi. Ruxsat berish:

<?php

header('Access-Control-Allow-Origin: https://mening-frontend.uz');
header('Access-Control-Allow-Methods: GET, POST, PATCH, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

[!WARNING] Access-Control-Allow-Origin: * — "har qanday sayt" degani. Ochiq, ma'lumotlari ommaviy API uchun normal. Foydalanuvchi ma'lumotlari bilan ishlaydigan API uchun — aniq domenlarni yozing.


Nima uchun shunday?

Nima uchun REST "fe'llarni URL ga yozmaslik" ni talab qiladi?

Chunki HTTP da fe'llar allaqachon bor — GET, POST, DELETE. Butun internet infratuzilmasi ularni tushunadi: proksi va CDN lar GET javoblarini keshlaydi, DELETE ni keshlamaydi; brauzer GET ni xavfsiz deb qayta yuboradi, POST uchun ogohlantiradi. /vazifaniOchir?id=5 ni GET bilan qilsangiz — bu infratuzilma sizga qarshi ishlaydi. REST — HTTP ni qanday yaratilgan bo'lsa, shunday ishlatish.


Xulosa

Eslab qoling:

  • Resurs — ot, ko'plikda: /api/vazifalar, /api/vazifalar/5
  • Amal — HTTP metodi: GET, POST, PATCH, DELETE
  • Status kodlar: 200, 201, 204, 400, 401, 404, 422
  • JSON tana: file_get_contents('php://input') + json_decode
  • Bazadagi qatorni to'g'ridan-to'g'ri emas — formatlangan holda qaytaring
  • Token bilan autentifikatsiya, CORS — aniq domenlar
  • Sinash — curl, Postman, REST Client

Keyingi dars

43-dars: Xavfsizlik — XSS, CSRF, SQL injection, sessiya o'g'irlash