[PHP Class] Tworzenie REST API w WordPressie z walidacją i rate-limitingiem

W tym wpisie opiszę, jak zbudo­wać lekkie, rozsze­rzal­ne REST API w WordPres­sie w czystym PHP, bazują­ce na abstrak­cyj­nej klasie, z mecha­ni­zmem API key, rate limitin­giem oraz prostą obsłu­gą danych.

Całość imple­men­ta­cji opiera się na dobrze udoku­men­to­wa­nych metodach PHP z wykorzy­sta­niem PHPDoc, dzięki czemu kod jest samodo­ku­men­tu­ją­cy i łatwy do dalszej rozbudowy.

<?php

namespace API;

use WP_REST_Server;
use WP_REST_Request;
use WP_Error;

/**
 * Abstrakcyjna klasa bazowa dla REST API w WordPress
 * 
 * Zapewnia:
 * - sprawdzanie klucza API
 * - rate limiting (sliding window)
 * - helper do zwracania danych
 * - helper do rejestracji endpointów
 */
abstract class AbstractApi {

   /**
    * Klucz API wymagany do autoryzacji
    * @var string
    */
   protected const API_KEY = 'MOJ_SEKRETNY_KLUCZ';

   /**
    * Maksymalna liczba wywołań w oknie czasowym
    * @var int
    */
   protected const RATE_LIMIT = 100;

   /**
    * Okno czasowe dla rate limit (w sekundach)
    * @var int
    */
   protected const RATE_WINDOW = 3600;

   /**
    * Sprawdza klucz API oraz limit wywołań (rate limit)
    * 
    * @param WP_REST_Request $request Obiekt żądania REST API
    * 
    * @return true|WP_Error
    *   - true jeśli klucz API poprawny i limit nie przekroczony
    *   - WP_Error w przypadku niepoprawnego klucza lub przekroczenia limitu
    */
   public static function checkApiKey(WP_REST_Request $request) {
      $key = (string) $request->get_param('api_key');
      if (!hash_equals(static::API_KEY, $key)) {
         return new WP_Error('forbidden', 'Nieprawidłowy klucz API', ['status' => 403]);
      }

      $transient_name = 'rate_' . md5($key);
      $history = get_transient($transient_name) ?: [];
      $now = time();
      $history = array_filter($history, fn($t) => $t > $now - static::RATE_WINDOW);

      if (count($history) >= static::RATE_LIMIT) {
         return new WP_Error(
            'rate_limit',
            'Przekroczono limit ' . static::RATE_LIMIT . ' wywołań na ' . (static::RATE_WINDOW / 3600) . 'h',
            ['status' => 429]
         );
      }

      $history[] = $now;
      set_transient($transient_name, $history, static::RATE_WINDOW);

      return true;
   }

   /**
    * Zwraca dane w formacie REST API (JSON)
    * 
    * @param mixed $data Dane do zwrócenia
    * @return \WP_REST_Response
    */
   protected static function respond($data) {
      return rest_ensure_response($data);
   }

   /**
    * Rejestruje endpoint REST API
    * 
    * @param string $url Ścieżka endpointu, np. '/wykonawcy'
    * @param string $callback Nazwa metody w klasie obsługującej endpoint
    * @param string|array $method Typ żądania HTTP (domyślnie GET)
    *   - WP_REST_Server::READABLE → GET
    *   - WP_REST_Server::CREATABLE → POST
    *   - WP_REST_Server::EDITABLE  → PUT/PATCH
    *   - WP_REST_Server::DELETABLE → DELETE
    * 
    * @return void
    */
   protected static function registerRoute(
      string $url,
      string $callback,
      string|array $method = WP_REST_Server::READABLE
   ) {
      register_rest_route('apr/v3', $url, [
         'methods'  => $method,
         'callback' => [static::class, $callback],
         'permission_callback' => [static::class, 'checkApiKey']
      ]);
   }

   /**
    * Rejestracja endpointów przez klasę potomną
    * 
    * @return void
    */
   abstract public static function register(): void;
}

/**
 * Klasa implementująca endpointy dla wykonawców
 */
class Wykonawcy extends AbstractApi {

   /**
    * Statyczna tablica przechowująca wykonawców (symulacja bazy)
    * @var array<int, array{id: int, nazwa: string}>
    */
   private static array $wykonawcy = [
      ['id' => 1, 'nazwa' => 'Metallica'],
      ['id' => 2, 'nazwa' => 'Queen']
   ];

   /**
    * Rejestracja endpointów REST API
    * 
    * @return void
    */
   public static function register(): void {
      self::registerRoute('/wykonawcy', 'endpointLista');
      self::registerRoute('/wykonawcy/add', 'endpointAdd', WP_REST_Server::CREATABLE);
   }

   /**
    * Endpoint GET /wykonawcy
    * Zwraca listę wszystkich wykonawców
    * 
    * @param WP_REST_Request $request Obiekt żądania
    * @return \WP_REST_Response Lista wykonawców
    */
   public static function endpointLista(WP_REST_Request $request) {
      return self::respond(self::$wykonawcy);
   }

   /**
    * Endpoint POST /wykonawcy/add
    * Dodaje nowego wykonawcę (symulacja w tablicy statycznej)
    * 
    * @param WP_REST_Request $request Obiekt żądania
    * @return \WP_REST_Response|WP_Error
    *   - WP_REST_Response z nowym wykonawcą
    *   - WP_Error jeśli pole "nazwa" jest puste
    */
   public static function endpointAdd(WP_REST_Request $request) {
      $name = sanitize_text_field($request->get_param('nazwa'));

      if (empty($name)) {
         return new WP_Error('invalid_data', 'Pole "nazwa" nie może być puste', ['status' => 400]);
      }

      $new_id = end(self::$wykonawcy)['id'] + 1;
      $nowy = ['id' => $new_id, 'nazwa' => $name];
      self::$wykonawcy[] = $nowy;

      return self::respond([
         'success' => true,
         'wykonawca' => $nowy
      ]);
   }
}

/**
 * Akcja inicjalizująca REST API
 */
add_action('rest_api_init', function () {
   \API\Wykonawcy::register();
});

Abstrakcyjna klasa bazowa: AbstractApi

Abstrak­cyj­na klasa AbstractApi jest funda­men­tem nasze­go API. Zawie­ra wszyst­kie mecha­ni­zmy wspól­ne dla wszyst­kich endpointów:

  • API Key – weryfi­ka­cja przy każdym żądaniu REST

  • Rate limiting – sliding window, aby ograni­czyć liczbę wywołań w określo­nym czasie

  • Helper do odpowie­dzi – respond() generu­je obiekt WP_REST_Response

  • Helper do rejestra­cji endpo­in­tów – registerRoute()

Kluczowe metody

checkApiKey(WP_REST_Request $request)

public static function checkApiKey(WP_REST_Request $request)
  • Spraw­dza, czy w żądaniu REST API przesła­no popraw­ny klucz API.

  • Weryfi­ku­je również, czy nie przekro­czo­no limit wywołań (100 wywołań na godzi­nę w przykładzie).

  • Zwraca true, jeśli wszyst­ko jest w porząd­ku, lub WP_Error w przypad­ku błędu.

Dzięki tej metodzie każdy endpo­int automa­tycz­nie korzy­sta z bezpiecz­nej autory­za­cji i ochro­ny przed nadmier­nym ruchem.

respond($data)

protected static function respond($data)
  • Ułatwia zwraca­nie danych w forma­cie JSON (WP_REST_Response).

  • Dzięki temu nie trzeba za każdym razem ręcznie tworzyć obiek­tów REST Response.

registerRoute(string $url, string $callback, string|array $method = WP_REST_Server::READABLE)

  • Rejestru­je endpo­int w WordPres­sie w ramach rest_api_init.

  • Obsłu­gu­je różne typy żądań: GET, POST, PUT/​PATCH, DELETE.

  • Automa­tycz­nie przypi­su­je checkApiKey jako permission_callback.

self::registerRoute('/wykonawcy', 'endpointLista');

Endpointy wykonawców: klasa Wykonawcy

Wykonawcy dziedzi­czy po AbstractApi i imple­men­tu­je dwa podsta­wo­we endpointy:

  1. GET /wykonawcy – zwraca listę wszyst­kich wykonawców

  2. POST /wykonawcy/add – dodaje nowego wykonaw­cę (symula­cja w tabli­cy PHP)

Endpoint GET /wykonawcy

public static function endpointLista(WP_REST_Request $request)
  • Zwraca statycz­ną tabli­cę wykonawców.

  • W pełnej imple­men­ta­cji można podłą­czyć tu bazę danych.

  • Odpowiedź jest automa­tycz­nie opako­wa­na w WP_REST_Response.

Przykła­do­wa odpowiedź JSON:

[
    {"id": 1, "nazwa": "Metallica"},
    {"id": 2, "nazwa": "Queen"}
]

Endpo­int POST /​wykonawcy/​add

public static function endpointAdd(WP_REST_Request $request)
  • Pobie­ra parametr nazwa z żądania POST i walidu­je go.

  • Jeśli pole jest puste – zwraca WP_Error z kodem 400.

  • Tworzy nowy wpis w tabli­cy statycz­nej (symula­cja dodania do bazy).

  • Zwraca nowy obiekt wykonaw­cy wraz z ID.

Przykła­do­wa odpowiedź JSON:

{
    "success": true,
    "wykonawca": {
        "id": 3,
        "nazwa": "Nirvana"
    }
}

Podsumowanie

Dzięki takie­mu podejściu:

  • Kod jest modular­ny – nowe endpo­in­ty można dodawać w klasach potomnych.

  • API jest bezpiecz­ne – każdy request jest weryfi­ko­wa­ny pod kątem klucza i limitu wywołań.

  • Kodu jest czytel­ny i dobrze udoku­men­to­wa­ny dzięki PHPDoc, co ułatwia dalszą rozbu­do­wę i integra­cję z IDE.

  • Można łatwo podłą­czyć bazę danych w przyszło­ści, zacho­wu­jąc mecha­ni­zmy bezpie­czeń­stwa i odpowie­dzi w JSON.

1 komentarz do “[PHP Class] Tworzenie REST API w WordPressie z walidacją i rate-limitingiem”

Dodaj komentarz

Ta strona używa Akismet do redukcji spamu. Dowiedz się, w jaki sposób przetwarzane są dane Twoich komentarzy.