PHP ile REST API Yazmak: Yönlendirme ve HTTP Durum Kodları

PHP ile REST API Yazmak: Yönlendirme ve HTTP Durum Kodları

5 dakika okuma

Bir API, arayüzden farklı olarak insan tarafından değil başka bir programla tüketilir. Tüketen taraf tahmin yürütemez: adreslerin öngörülebilir, durum kodlarının doğru ve hata biçiminin tutarlı olması gerekir. Bu bölümde saf PHP ile küçük bir API'nin nasıl kurulacağı ele alınıyor.

Amaç bir çatı yazmak değil, hazır çatıların hangi sorunları çözdüğünü anlamaktır. Aynı ilkeler, kullanılan araç ne olursa olsun geçerlidir.

Kaynak adlandırma

Adresler eylemi değil kaynağı gösterir; eylem HTTP yöntemiyle belirtilir. Kaynak adları çoğul yazılır ve iç içe geçmiş ilişkiler adres yapısında gösterilir.

  • GET /api/v1/yazilar: listeleme
  • GET /api/v1/yazilar/42: tek kayıt
  • POST /api/v1/yazilar: oluşturma
  • PATCH /api/v1/yazilar/42: kısmi güncelleme
  • DELETE /api/v1/yazilar/42: silme
  • GET /api/v1/yazilar/42/yorumlar: alt kaynak

Adreslerde eylem adı kullanılması, aynı işi yapan birden çok uç oluşmasına yol açar. Kaynak odaklı düzende ise yeni bir işlem eklendiğinde adres yapısı değişmez; yalnızca yeni bir yöntem desteklenir. Bu tutarlılık, tüketen tarafın belgelere daha az bakmasını sağlar.

Sürüm numarasının adreste bulunması, ileride uyumluluğu bozan bir değişiklik yapılması gerektiğinde eski tüketicilerin çalışmaya devam etmesini sağlar.

Basit bir yönlendirici

Yönlendirici, yöntem ve yol ikilisini bir işleyiciye eşler. Yoldaki değişken bölümler desene çevrilir ve yakalanan değerler işleyiciye aktarılır.

PHP29 satır
final class Yonlendirici
{
    /** @var list<array{yontem:string, desen:string, isleyici:callable}> */
    private array $rotalar = [];

    public function ekle(string $yontem, string $yol, callable $isleyici): void
    {
        $desen = '#^' . preg_replace('#\{(\w+)\}#', '(?<$1>[^/]+)', $yol) . '$#';

        $this->rotalar[] = ['yontem' => $yontem, 'desen' => $desen, 'isleyici' => $isleyici];
    }

    public function calistir(string $yontem, string $yol): mixed
    {
        foreach ($this->rotalar as $rota) {
            if ($rota['yontem'] !== $yontem) {
                continue;
            }

            if (preg_match($rota['desen'], $yol, $p) === 1) {
                $parametre = array_filter($p, static fn ($k) => !is_int($k), ARRAY_FILTER_USE_KEY);

                return ($rota['isleyici'])($parametre);
            }
        }

        throw new BulunamadiHatasi();
    }
}
PHP12 satır
$yol = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$yontem = $_SERVER['REQUEST_METHOD'];

$router = new Yonlendirici();
$router->ekle('GET', '/api/v1/yazilar', $listele);
$router->ekle('GET', '/api/v1/yazilar/{id}', $getir);

try {
    $router->calistir($yontem, rtrim($yol, '/'));
} catch (BulunamadiHatasi) {
    jsonYanit(['hata' => 'Kaynak bulunamadı'], 404);
}

Durum kodları

Durum kodu, yanıtın gövdesi okunmadan önce sonucu anlatır. Yanlış kod kullanmak, tüketen tarafta hatalı davranışa yol açar: başarısız bir işlem 200 koduyla dönerse istemci hata denetimi yapmaz.

  • 200: başarılı okuma ya da güncelleme
  • 201: kayıt oluşturuldu, Location başlığı eklenir
  • 204: başarılı, gövde yok (silme işlemleri)
  • 400: istek biçimi hatalı
  • 401: kimlik doğrulanmadı, 403: yetki yok
  • 404: kaynak yok, 409: çakışma
  • 422: biçim doğru ancak doğrulama başarısız
  • 429: hız sınırı aşıldı, 500: sunucu hatası

Kimlik doğrulanmadı ile yetki yok ayrımı sık karıştırılır. Birincisi kimliğin hiç ya da geçersiz sunulduğunu, ikincisi kimliğin geçerli olduğunu ancak bu işlem için yeterli olmadığını anlatır.

Kimlik doğrulama

API isteklerinde oturum çerezi yerine yetkilendirme başlığı kullanılır. Belirteç sunucu tarafında saklanır ve karşılaştırma sabit zamanlı yapılır.

PHP11 satır
$baslik = $_SERVER['HTTP_AUTHORIZATION'] ?? '';

if (!str_starts_with($baslik, 'Bearer ')) {
    jsonYanit(['hata' => 'Yetkilendirme başlığı eksik'], 401);
}

$belirtec = substr($baslik, 7);

if (!hash_equals($beklenen, $belirtec)) {
    jsonYanit(['hata' => 'Geçersiz belirteç'], 401);
}

Belirteçler veritabanında düz olarak saklanmaz; özetleri tutulur ve karşılaştırma özet üzerinden yapılır. Böylece veritabanı kopyası ele geçse bile belirteçler doğrudan kullanılamaz. Her belirtecin bir son kullanma tarihi ve iptal edilebilme yolu bulunmalıdır.

Bazı sunucu yapılandırmalarında yetkilendirme başlığı PHP'ye aktarılmaz. Bu durumda sunucu tarafında ilgili yönlendirme kuralının eklenmesi gerekir; aksi halde doğru gönderilen istekler de reddedilir.

Doğrulama ve hata biçimi

Hataların tek bir biçimde dönmesi, tüketen tarafın tek bir çözümleme kodu yazmasını sağlar. Alan bazlı doğrulama hataları ayrı ayrı listelenir.

PHP13 satır
$hatalar = [];

if (!is_string($veri['baslik'] ?? null) || mb_strlen(trim($veri['baslik'])) < 3) {
    $hatalar['baslik'] = 'En az üç karakter olmalı';
}

if (!in_array($veri['durum'] ?? '', ['taslak', 'yayinda'], strict: true)) {
    $hatalar['durum'] = 'Geçersiz değer';
}

if ($hatalar !== []) {
    jsonYanit(['hata' => 'Doğrulama başarısız', 'alanlar' => $hatalar], 422);
}

Listeleme, sayfalama ve süzme

Liste uçlarında üst sınır konulmaması, tek bir isteğin tüm tabloyu belleğe almasına yol açar. Sayfa boyutu istemciden alınır, ancak sunucu tarafında sınırlanır.

PHP12 satır
$adet = min(max((int) ($_GET['adet'] ?? 20), 1), 100);
$sayfa = max((int) ($_GET['sayfa'] ?? 1), 1);
$atla = ($sayfa - 1) * $adet;

jsonYanit([
    'veri' => $kayitlar,
    'sayfalama' => [
        'sayfa' => $sayfa,
        'adet' => $adet,
        'toplam' => $toplam,
    ],
]);

Süzme ve sıralama ölçütleri de sunucu tarafında sınırlanır. İstemcinin belirlediği sütun adının doğrudan sorguya girmesi, hem enjeksiyon riski taşır hem de indekslenmemiş sütunlarda ağır sorgular üretir. İzin verilen ölçütler listelenir ve dışındakiler reddedilir.

Çok büyük tablolarda sayfa numarasına dayalı atlama, ilerleyen sayfalarda yavaşlar. Sıralama ölçütüne göre son kaydın anahtarını taşıyan imleç tabanlı sayfalama bu durumda daha uygundur.

Önbellek ve koşullu istekler

Sık değişmeyen kaynaklarda, aynı veriyi tekrar tekrar göndermek yerine istemcinin elindeki kopyanın hala geçerli olduğunu bildirmek daha ucuzdur. Yanıtla birlikte gönderilen etiket, sonraki istekte geri gönderilir ve içerik değişmemişse gövdesiz bir yanıt döner.

PHP11 satır
$etiket = '"' . md5($icerikOzeti) . '"';

header('ETag: ' . $etiket);
header('Cache-Control: private, max-age=60');

if (($_SERVER['HTTP_IF_NONE_MATCH'] ?? '') === $etiket) {
    http_response_code(304);      // gövde gönderilmez
    exit;
}

jsonYanit($veri);

Hız sınırlaması da API'nin ayrılmaz parçasıdır. Sınıra ulaşan istemciye 429 kodu ile birlikte ne kadar beklemesi gerektiği bildirilir; bu bilgi olmadan istemciler genellikle hemen yeniden dener ve yük daha da artar.

PHP7 satır
header('X-RateLimit-Limit: 100');
header('X-RateLimit-Remaining: ' . $kalan);

if ($kalan <= 0) {
    header('Retry-After: 60');
    jsonYanit(['hata' => 'Hız sınırı aşıldı'], 429);
}

Kontrol listesi

  • Adresler kaynak adı taşıyor, eylem HTTP yöntemiyle belirtiliyor.
  • Sürüm numarası adres yapısında yer alıyor.
  • Her yanıtta doğru durum kodu ve Content-Type başlığı var.
  • Hata gövdeleri tek biçimde; doğrulama hataları alan bazlı dönüyor.
  • Kimlik doğrulama başlıkla yapılıyor, karşılaştırma sabit zamanlı.
  • Liste uçlarında sayfa boyutu sunucu tarafında sınırlanıyor.
  • Sunucu hataları istemciye ayrıntı sızdırmıyor, günlüğe yazılıyor.

İlgili yazılar

PHP eğitim serisi

Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.

Temeller

  1. Kurulum ve temel sözdizimi
  2. Döngüler ve akış kontrolü
  3. Diziler
  4. Fonksiyonlar
  5. String fonksiyonları
  6. Süper globaller
  7. Form işleme
  8. Dosya işlemleri

Dilin ayrıntıları

  1. Tip sistemi ve strict_types
  2. Hata ve istisna yönetimi
  3. Düzenli ifadeler
  4. Tarih ve saat işlemleri

Nesne yönelimli PHP

  1. Sınıf ve nesne
  2. Kalıtım, arayüz ve trait
  3. Statik üyeler, sabitler ve enum
  4. Namespace, Composer ve autoload

Veri ve güvenlik

  1. PDO ile veritabanı işlemleri
  2. Oturum yönetimi ve kimlik doğrulama
  3. Güvenli çıktı ve XSS
  4. JSON ile veri değişimi

Dış dünya ve üretim

  1. cURL ile dış API çağırma
  2. Kendi REST API'ni yazmak (bu yazı)
  3. PHPUnit ile birim test
  4. Hata ayıklama ve performans ölçümü
  5. Üretime alma ve güvenlik

Yorumlar

Bu form reCAPTCHA ile korunur; Google Gizlilik Politikası ve Hizmet Şartları geçerlidir.

İlk yorumu sen yap.

İlgili Yazılar