18-bo‘lim

JSON va API

JSON kodlash va dekodlash, REST API yaratish, HTTP holat kodlari va tashqi API ga so'rov yuborish.

🕑 10 daqiqa o‘qish 📄 654 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. JSON kodlash
  2. JSON dekodlash
  3. API javobini shakllantirish
  4. Kichik REST API
  5. Tashqi API ga so'rov
  6. Xulosa

Mobil ilova, JavaScript va boshqa xizmatlar bilan gaplashish uchun sahifa emas, ma'lumot kerak. Bugun bu ma'lumot deyarli har doim JSON shaklida uzatiladi.

JSON kodlash #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
const JSON_BAYROQLAR = JSON_UNESCAPED_UNICODE
                     | JSON_UNESCAPED_SLASHES
                     | JSON_THROW_ON_ERROR;
PHP
$mahsulot = [
    "id"      => 7,
    "nom"     => "Klaviatura",
    "narx"    => 350000,
    "omborda" => true,
    "turkum"  => null,
    "teglar"  => ["aksessuar", "simsiz"],
    "havola"  => "https://softromeda.uz/mahsulot/7",
];

echo "Bayroqlarsiz:\n";
echo json_encode($mahsulot) . "\n";

echo "\nTo'g'ri bayroqlar bilan:\n";
echo json_encode($mahsulot, JSON_BAYROQLAR) . "\n";

echo "\nO'qishga qulay shaklda:\n";
echo json_encode($mahsulot, JSON_BAYROQLAR | JSON_PRETTY_PRINT) . "\n";

echo "\n";
// ASCII bo'lmagan belgilar bilan farq.
// "\u{02BB}" - o'zbek tutuq belgisi (4-bo'limda ko'rgan edik).
$uz = ["shahar" => "O\u{02BB}zbekiston", "belgi" => "\u{1F418}"];
echo "UNESCAPED_UNICODE siz:   " . json_encode($uz) . "\n";
echo "UNESCAPED_UNICODE bilan: " . json_encode($uz, JSON_BAYROQLAR) . "\n";
echo "\n";
echo "Birinchi qatorda belgilar \\uXXXX kodiga aylandi - JSON to'g'ri,\n";
echo "lekin uni odam o'qiy olmaydi va fayl hajmi ortadi.\n";
Natija
Bayroqlarsiz:
{"id":7,"nom":"Klaviatura","narx":350000,"omborda":true,"turkum":null,"teglar":["aksessuar","simsiz"],"havola":"https:\/\/softromeda.uz\/mahsulot\/7"}

To'g'ri bayroqlar bilan:
{"id":7,"nom":"Klaviatura","narx":350000,"omborda":true,"turkum":null,"teglar":["aksessuar","simsiz"],"havola":"https://softromeda.uz/mahsulot/7"}

O'qishga qulay shaklda:
{
    "id": 7,
    "nom": "Klaviatura",
    "narx": 350000,
    "omborda": true,
    "turkum": null,
    "teglar": [
        "aksessuar",
        "simsiz"
    ],
    "havola": "https://softromeda.uz/mahsulot/7"
}

UNESCAPED_UNICODE siz:   {"shahar":"O\u02bbzbekiston","belgi":"\ud83d\udc18"}
UNESCAPED_UNICODE bilan: {"shahar":"Oʻzbekiston","belgi":"🐘"}

Birinchi qatorda belgilar \uXXXX kodiga aylandi - JSON to'g'ri,
lekin uni odam o'qiy olmaydi va fayl hajmi ortadi.
Massiv JSON da obyektga aylanib qolishi mumkin

5-bo'limda aytgan edik: array_filter kalitlarni saqlaydi. Endi uning oqibatini ko'ramiz.

PHP
$sonlar = [1, 2, 3, 4, 5];
$juftlar = array_filter($sonlar, fn($x) => $x % 2 === 0);

json_encode($juftlar);                    // {"1":2,"3":4}
json_encode(array_values($juftlar));      // [2,4]

Birinchi natija obyekt, ikkinchisi massiv. JavaScript tomonida bu katta farq:

JavaScript
data.forEach(...)      // obyektda ishlamaydi
data.length            // obyektda undefined

Qoida: API dan ro'yxat qaytarayotgan bo'lsangiz, undan oldin har doim array_values() chaqiring.

Bo'sh massiv bilan ham xuddi shu muammo bor:

PHP qiymatiJSON
[][]
(object) []{}
Filtrlangan [][]

Agar API doim obyekt qaytarishi kerak bo'lsa, JSON_FORCE_OBJECT bayrog'i bor - lekin uni butun javobga emas, aniq joyga qo'llang.

JSON dekodlash #

PHP
$xom = '{"id":7,"nom":"Klaviatura","narx":350000,"omborda":true,"teglar":["a","b"]}';

// Assotsiativ massiv sifatida (ikkinchi argument true)
$massiv = json_decode($xom, true, 512, JSON_THROW_ON_ERROR);
echo "Massiv sifatida:\n";
printf("  nom:     %s\n", $massiv["nom"]);
printf("  narx:    %d (turi: %s)\n", $massiv["narx"], get_debug_type($massiv["narx"]));
printf("  omborda: %s (turi: %s)\n",
       var_export($massiv["omborda"], true), get_debug_type($massiv["omborda"]));

echo "\n";
// Obyekt sifatida (ikkinchi argument false yoki berilmagan)
$obyekt = json_decode($xom, false, 512, JSON_THROW_ON_ERROR);
echo "Obyekt sifatida:\n";
printf("  nom:   %s\n", $obyekt->nom);
printf("  sinf:  %s\n", $obyekt::class);
printf("  teg 1: %s\n", $obyekt->teglar[0]);

echo "\n";
// Buzuq JSON
$buzuqlar = ['{"a":1,}', "{'a':1}", '{"a":}', "salom", ""];
echo "Buzuq JSON larni tekshirish:\n";
foreach ($buzuqlar as $b) {
    try {
        json_decode($b, true, 512, JSON_THROW_ON_ERROR);
        echo "  " . str_pad("'{$b}'", 12) . " OK\n";
    } catch (JsonException $x) {
        echo "  " . str_pad("'{$b}'", 12) . " " . $x->getMessage() . "\n";
    }
}
Natija
Massiv sifatida:
  nom:     Klaviatura
  narx:    350000 (turi: int)
  omborda: true (turi: bool)

Obyekt sifatida:
  nom:   Klaviatura
  sinf:  stdClass
  teg 1: a

Buzuq JSON larni tekshirish:
  '{"a":1,}'   Syntax error
  '{'a':1}'    Syntax error
  '{"a":}'     Syntax error
  'salom'      Syntax error
  ''           Syntax error
API javobida to'g'ri holat kodini bering 2xx - MUVAFFAQIYAT 200 OK o'qish muvaffaqiyatli 201 Created yangi yozuv yaratildi 204 No Content o'chirildi, tana yo'q 201 bilan Location sarlavhasini ham bering 4xx - MIJOZ XATOSI 400 Bad Request noto'g'ri so'rov 401 Unauthorized kirmagansiz 403 Forbidden huquq yo'q 404 Not Found topilmadi 401 - kimligingiz noma'lum; 403 - ma'lum, huquq yo'q 5xx - SERVER XATOSI 500 Internal Error 503 Unavailable Bu SIZNING xatongiz - jurnalga yozilishi shart Mijozga tafsilot BERILMAYDI Eng keng tarqalgan xato: har doim 200 qaytarish Xato bo'lsa ham 200 OK + {"error": "..."} - mijoz kutubxonalari buni muvaffaqiyat deb biladi. REST: URL - resurs, usul - amal GET /api/mahsulotlar ro'yxat POST /api/mahsulotlar yaratish GET /api/mahsulotlar/7 bittasi PUT /api/mahsulotlar/7 yangilash URL da fe'l bo'lmasin: /api/mahsulotOchir emas, DELETE /api/mahsulotlar/7.
Holat kodi javobning bir qismi - uni to'g'ri tanlang

API javobini shakllantirish #

PHP
final class ApiJavob
{
    public function __construct(
        public readonly int $kod,
        public readonly array $tana,
    ) {}

    public static function ok(array $malumot): self
    {
        return new self(200, ["ok" => true, "data" => $malumot]);
    }

    public static function yaratildi(array $malumot): self
    {
        return new self(201, ["ok" => true, "data" => $malumot]);
    }

    public static function xato(int $kod, string $xabar, array $tafsilot = []): self
    {
        $tana = ["ok" => false, "error" => ["xabar" => $xabar]];
        if ($tafsilot !== []) {
            $tana["error"]["maydonlar"] = $tafsilot;
        }
        return new self($kod, $tana);
    }

    public function chiqar(): string
    {
        // Haqiqiy kodda: http_response_code($this->kod);
        //                header("Content-Type: application/json; charset=utf-8");
        return sprintf(
            "HTTP %d\n%s",
            $this->kod,
            json_encode($this->tana, JSON_BAYROQLAR | JSON_PRETTY_PRINT)
        );
    }
}

$javoblar = [
    ApiJavob::ok(["id" => 7, "nom" => "Klaviatura"]),
    ApiJavob::yaratildi(["id" => 12]),
    ApiJavob::xato(404, "Mahsulot topilmadi"),
    ApiJavob::xato(422, "Ma'lumot noto'g'ri", [
        "narx" => "Musbat son bo'lishi kerak",
        "nom"  => "Bo'sh bo'lmasin",
    ]),
];

foreach ($javoblar as $j) {
    echo $j->chiqar() . "\n\n";
}
Natija
HTTP 200
{
    "ok": true,
    "data": {
        "id": 7,
        "nom": "Klaviatura"
    }
}

HTTP 201
{
    "ok": true,
    "data": {
        "id": 12
    }
}

HTTP 404
{
    "ok": false,
    "error": {
        "xabar": "Mahsulot topilmadi"
    }
}

HTTP 422
{
    "ok": false,
    "error": {
        "xabar": "Ma'lumot noto'g'ri",
        "maydonlar": {
            "narx": "Musbat son bo'lishi kerak",
            "nom": "Bo'sh bo'lmasin"
        }
    }
}
Javob shakli butun API bo'ylab bir xil bo'lsin

Yuqoridagi sinf har bir javobga bir xil tuzilma beradi:

JSON
{"ok": true,  "data": {...}}
{"ok": false, "error": {"xabar": "..."}}

Bu mijoz tomonini ancha soddalashtiradi - bitta funksiya barcha javoblarni qayta ishlaydi.

Validatsiya xatolari uchun 422 Unprocessable Entity ishlatiladi: so'rov shakli to'g'ri, lekin mazmuni qoidalarga mos emas. 400 Bad Request esa so'rovning o'zi buzuq bo'lganda (masalan JSON tahlil qilinmasa).

Xato javobida qaysi maydon xato ekanini ko'rsating - mijoz uni formada tegishli joyga chiqara oladi.

Va eng muhimi: ok maydoniga tayanmang. Mijoz avvalo HTTP holat kodini tekshirsin. ok maydoni faqat qulaylik uchun.

Kichik REST API #

PHP
<?php
// public/api.php
declare(strict_types=1);

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

header("Content-Type: application/json; charset=utf-8");

$usul = $_SERVER["REQUEST_METHOD"];
$yol  = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) ?? "/";
$qism = array_values(array_filter(explode("/", trim($yol, "/"))));

// POST/PUT tanasini o'qish - $_POST JSON ni TUSHUNMAYDI
$tana = [];
if (in_array($usul, ["POST", "PUT", "PATCH"], true)) {
    $xom = file_get_contents("php://input");
    try {
        $tana = $xom === "" ? [] : json_decode($xom, true, 512, JSON_THROW_ON_ERROR);
    } catch (JsonException) {
        http_response_code(400);
        echo json_encode(["ok" => false, "error" => ["xabar" => "JSON buzuq"]]);
        exit;
    }
}

// Marshrutlash
$javob = match (true) {
    $usul === "GET"    && $qism === ["api", "mahsulotlar"]
        => ApiJavob::ok($ombor->royxat()),

    $usul === "GET"    && count($qism) === 3 && $qism[1] === "mahsulotlar"
        => ($m = $ombor->top((int) $qism[2])) !== null
            ? ApiJavob::ok($m)
            : ApiJavob::xato(404, "Topilmadi"),

    $usul === "POST"   && $qism === ["api", "mahsulotlar"]
        => ApiJavob::yaratildi($ombor->qosh($tana)),

    $usul === "DELETE" && count($qism) === 3 && $qism[1] === "mahsulotlar"
        => $ombor->ochir((int) $qism[2])
            ? new ApiJavob(204, [])
            : ApiJavob::xato(404, "Topilmadi"),

    default => ApiJavob::xato(404, "Bunday manzil yo'q"),
};

http_response_code($javob->kod);
if ($javob->kod !== 204) {
    echo json_encode($javob->tana, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}
$_POST JSON tanasini o'qimaydi

Bu juda ko'p vaqt yo'qotadigan chalkashlik.

$_POST faqat ikki holatda to'ladi:

Content-Type$_POST to'ladimi
application/x-www-form-urlencodedHa
multipart/form-dataHa
application/jsonYo'q - bo'sh qoladi

JavaScript dan fetch bilan JSON yuborganingizda $_POST bo'sh bo'ladi va kod "ma'lumot kelmadi" deb o'ylaydi.

To'g'ri usul - xom tanani o'qish:

PHP
$xom = file_get_contents("php://input");
$tana = json_decode($xom, true, 512, JSON_THROW_ON_ERROR);

Diqqat: php://input ni faqat bir marta o'qish mumkin (ba'zi sozlamalarda). Shuning uchun uni o'zgaruvchiga saqlab qo'ying.

Va PUT/PATCH/DELETE uchun $_POST hech qachon to'lmaydi - usuldan qat'i nazar php://input kerak.

Tashqi API ga so'rov #

PHP
<?php
declare(strict_types=1);

function apiSorov(string $url, array $tana = [], array $sarlavhalar = []): array
{
    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,          // MAJBURIY - aks holda abadiy kutadi
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_SSL_VERIFYPEER => true,        // HECH QACHON false qilmang
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_FOLLOWLOCATION => false,       // yo'naltirishni o'zingiz boshqaring
        CURLOPT_HTTPHEADER     => array_merge(
            ["Content-Type: application/json", "Accept: application/json"],
            $sarlavhalar
        ),
    ]);

    if ($tana !== []) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS,
            json_encode($tana, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
    }

    $javob = curl_exec($ch);
    $kod   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $xato  = curl_error($ch);
    curl_close($ch);

    if ($javob === false) {
        throw new RuntimeException("So'rov yuborilmadi: {$xato}");
    }

    if ($kod >= 400) {
        throw new RuntimeException("API xato qaytardi: HTTP {$kod}");
    }

    return json_decode((string) $javob, true, 512, JSON_THROW_ON_ERROR);
}
CURLOPT_SSL_VERIFYPEER => false - hech qachon

Internetda "sertifikat xatosi" muammosiga eng ko'p beriladigan "yechim" shu. U xato.

Bu sozlama TLS ni butunlay ma'nosiz qiladi: siz shifrli kanalda gaplashasiz, lekin kim bilan gaplashayotganingizni tekshirmaysiz. O'rtadagi odam hujumi ochiq qoladi.

Haqiqiy sabab odatda eskirgan CA sertifikatlar to'plami. To'g'ri yechim:

PHP
curl_setopt($ch, CURLOPT_CAINFO, "/yol/cacert.pem");

cacert.pem ni curl.se/docs/caextract.html dan olish mumkin, yoki php.ini da curl.cainfo ni ko'rsating.

Yana uch muhim sozlama:

SozlamaNima uchun
CURLOPT_TIMEOUTUsiz so'rov abadiy kutishi mumkin
CURLOPT_CONNECTTIMEOUTUlanish bosqichi uchun alohida
CURLOPT_FOLLOWLOCATION => falseYo'naltirish ichki tarmoqqa olib borishi mumkin

Birinchisi eng ko'p muammo keltiradi: taymautsiz bitta sekin tashqi API butun saytingizni to'xtatib qo'yadi.

Amaliy topshiriq
  1. Massivni json_encode bilan kodlang va bayroqlarni solishtiring.
  2. O'zbek harflarini JSON_UNESCAPED_UNICODE siz kodlang.
  3. array_filter natijasini JSON ga aylantiring - massivmi yoki obyekt?
  4. array_values() qo'shib qayta sinang.
  5. json_decode ni true va false ikkinchi argument bilan chaqiring.
  6. Buzuq JSON bering va JsonException ni tuting.
  7. Bir xil shaklda javob qaytaruvchi ApiJavob sinfini yozing.
  8. 404 va 422 uchun to'g'ri holat kodini bering.
  9. fetch bilan JSON yuborib, $_POST bo'shligini tekshiring.
  10. php://input dan o'qib, muammoni hal qiling.

Xulosa #

  • JSON kodlashda JSON_UNESCAPED_UNICODE, JSON_UNESCAPED_SLASHES va JSON_THROW_ON_ERROR ishlating.
  • Filtrlangan massiv JSON da obyektga aylanadi - array_values() ni unutmang.
  • json_decode ning ikkinchi argumenti true bo'lsa massiv, false bo'lsa obyekt qaytadi.
  • JSON_THROW_ON_ERROR siz buzuq JSON null beradi va uni haqiqiy null dan ajratib bo'lmaydi.
  • API javobi butun loyiha bo'ylab bir xil shaklda bo'lsin.
  • HTTP holat kodini to'g'ri bering - xatoda ham 200 qaytarmang.
  • 401 - kim ekaningiz noma'lum; 403 - ma'lum, lekin huquq yo'q.
  • Validatsiya xatosi uchun 422, buzuq so'rov uchun 400.
  • REST da URL resurs, HTTP usuli esa amal - URL da fe'l bo'lmasin.
  • $_POST JSON tanasini o'qimaydi - php://input dan o'qing.
  • cURL da CURLOPT_TIMEOUT majburiy, SSL_VERIFYPEER => false esa taqiqlangan.

Keyingi bo'limda PHP 8 ning zamonaviy imkoniyatlarini ko'rib chiqamiz.

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.