# Poradnik API filmów w PHP

> Zbuduj jedną funkcję wielokrotnego użytku w PHP i malutką stronę filmu. Funkcja wywołuje amodb przez cURL, czyta Retry-After po 429 i zapisuje każdą odpowiedź w pliku na 6 godzin. Strona pokazuje plakat, obsadę i wynik IMDb. Wymaga PHP 8.2 lub nowszego z rozszerzeniem curl i zajmuje około 20 minut.

Zbudujesz dwa małe pliki. `amodb.php` zawiera jedną funkcję, `amodb_get()`, która wywołuje API przez cURL i zapisuje odpowiedzi w plikach. `movie.php` to strona, która pokazuje film po ID z plakatem, obsadą i wynikiem IMDb. Potrzebujesz PHP 8.2 lub nowszego z rozszerzeniem `curl` i około 20 minut.

## Krok 1: Zdobądź klucz API

Kup okres przedpłaty na stronie [cennik](https://amodb.dev/pl/pricing/). Nie ma darmowego planu, a każdy plan zwraca pełny zbiór danych przy 20, 40 lub 120 żądaniach na minutę. Utwórz klucz w panelu konta. To 32 małe znaki szesnastkowe. Wyeksportuj go, zanim uruchomisz PHP:

```bash
export AMODB_KEY="YOUR_API_KEY"
```

Kod czyta go przez `getenv('AMODB_KEY')`. Pod PHP-FPM workery nie widzą twojego shella, więc ustaw zamiast tego `env[AMODB_KEY]` w konfiguracji puli.

## Krok 2: Przygotuj projekt

Utwórz folder i sprawdź, czy cURL jest dostępny:

```bash
mkdir movie-page && cd movie-page
php -v
php -m | grep -i curl
```

Pierwsze polecenie powinno zgłosić 8.2 lub wyższe. Drugie musi wypisać `curl`.

## Krok 3: Wyślij pierwsze żądanie

Samo wywołanie cURL potrzebuje czterech rzeczy: URL, timeoutu, nagłówka `Authorization` i sprawdzenia statusu. W PHP 8 uchwyt zwalnia się sam, więc nie ma `curl_close()`.

```php
<?php
$ch = curl_init('https://api.amodb.dev/3/movie/550?language=en-US');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AMODB_KEY')],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), PHP_EOL;
```

## Krok 4: Owiń to w funkcję

Wywoływanie cURL z każdej strony powtarza tę samą konfigurację. `amodb_get($path, $query, $ttl)` buduje URL przez `http_build_query`, wysyła wywołanie i zwraca zdekodowaną tablicę. Strona filmu przekazuje `append_to_response=credits,ratings`, więc obsada i wyniki przychodzą w tym samym żądaniu. Szczegóły pól są na stronie [filmy](https://amodb.dev/pl/movies/), a rozmiary plakatów na stronie [obrazy](https://amodb.dev/pl/images-api/).

## Krok 5: Obsłuż błędy i limity żądań

Funkcja rzuca `AmodbException` przy każdej awarii, a strona łapie ją w jednym miejscu.

| Status    | Co robi funkcja                                                                              |
| --------- | -------------------------------------------------------------------------------------------- |
| 401       | Rzuca wyjątek od razu. Złego lub zawieszonego klucza nie naprawi ponawianie.                 |
| 404       | Rzuca wyjątek z `status_message`, by strona mogła powiedzieć, że ID jest nieznane.           |
| 429       | Czyta nagłówek `Retry-After`, śpi tyle sekund (od 1 do 30), potem ponawia najwyżej dwa razy. |
| 5xx       | Rzuca wyjątek z kodem statusu i komunikatem.                                                 |
| Błąd cURL | Rzuca `Network error` z tekstem z `curl_error()`.                                            |

Nagłówek przychodzi przez `CURLOPT_HEADERFUNCTION`, callback, który cURL uruchamia dla każdej linii nagłówka odpowiedzi. Pełną listę kodów znajdziesz na stronie [błędy](https://amodb.dev/pl/errors/).

## Krok 6: Zapisuj odpowiedzi w plikach

Edge trzyma szczegóły przez 6 godzin. Lokalny plik oszczędza samo żądanie, a trafienie w cache nie kosztuje nic z twojego limitu. Wiek się sumuje, więc plik mający 6 godzin może zawierać dane, które na edge były już sprzed godzin. Obniż TTL, jeśli to ma znaczenie. Funkcja haszuje końcowy URL przez `sha1`, szuka pliku o tej nazwie i zwraca go, gdy jego czas modyfikacji jest nowszy niż TTL. W przeciwnym razie pobiera dane, zapisuje treść z `LOCK_EX` i zwraca dane.

## Krok 7: Kompletny program

Zapisz funkcję jako `amodb.php`.

```php
<?php
declare(strict_types=1);

const AMODB_BASE = 'https://api.amodb.dev/3';

final class AmodbException extends RuntimeException
{
}

function amodb_get(string $path, array $query = [], int $ttl = 21600): array
{
    $key = getenv('AMODB_KEY');
    if ($key === false || $key === '') {
        throw new AmodbException('Set the AMODB_KEY environment variable.');
    }

    $url = AMODB_BASE . $path . ($query ? '?' . http_build_query($query) : '');
    $dir = sys_get_temp_dir() . '/amodb-cache';
    $file = $dir . '/' . sha1($url) . '.json';
    if (is_file($file) && time() - filemtime($file) < $ttl) {
        return json_decode((string) file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);
    }

    for ($attempt = 1; $attempt <= 3; $attempt++) {
        $retryAfter = 1;
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT => 10,
            CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Accept: application/json'],
            CURLOPT_HEADERFUNCTION => static function ($handle, string $line) use (&$retryAfter): int {
                if (stripos($line, 'retry-after:') === 0) {
                    $retryAfter = (int) trim(substr($line, 12));
                }
                return strlen($line);
            },
        ]);
        $body = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);

        if ($body === false) {
            throw new AmodbException('Network error: ' . $error);
        }
        if ($status !== 429 || $attempt === 3) {
            break;
        }
        sleep(max(1, min($retryAfter, 30)));
    }

    if ($status === 401) {
        throw new AmodbException('401: the API key is invalid or suspended.');
    }
    if ($status !== 200) {
        $detail = json_decode((string) $body, true)['status_message'] ?? 'no message';
        throw new AmodbException("amodb returned $status: $detail");
    }

    if (!is_dir($dir)) {
        mkdir($dir, 0775, true);
    }
    file_put_contents($file, $body, LOCK_EX);

    return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
```

Potem zapisz stronę jako `movie.php`. Każda wartość przechodzi przez `htmlspecialchars`, zanim trafi do HTML.

```php
<?php
declare(strict_types=1);

require __DIR__ . '/amodb.php';

function h(?string $text): string
{
    return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8');
}

$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 550;
$movie = null;
$error = null;
try {
    $movie = amodb_get("/movie/$id", ['language' => 'en-US', 'append_to_response' => 'credits,ratings']);
} catch (AmodbException $e) {
    http_response_code(502);
    $error = $e->getMessage();
}
$imdb = $movie['ratings']['imdb'] ?? null;
$cast = $movie ? array_slice(array_column($movie['credits']['cast'], 'name'), 0, 3) : [];
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title><?= h($movie['title'] ?? 'Movie lookup') ?></title>
</head>
<body>
<?php if ($error !== null): ?>
  <p>Could not load the movie: <?= h($error) ?></p>
<?php else: ?>
  <h1><?= h($movie['title']) ?> (<?= h(substr((string) ($movie['release_date'] ?? ''), 0, 4)) ?>)</h1>
  <?php if (!empty($movie['poster_path'])): ?>
    <img src="https://img.amodb.dev/t/p/w342<?= h($movie['poster_path']) ?>" alt="Poster of <?= h($movie['title']) ?>" width="342">
  <?php endif; ?>
  <p><?= h($movie['overview'] ?? '') ?></p>
  <p>Cast: <?= h(implode(', ', $cast)) ?></p>
  <p>IMDb: <?= $imdb ? h((string) $imdb['value']) : 'not known' ?></p>
<?php endif; ?>
</body>
</html>
```

Uruchom wbudowany serwer przez `php -S localhost:8000` i otwórz `http://localhost:8000/movie.php?id=603`. URL plakatu wskazuje na hosta obrazów, który nie wymaga klucza.

## Kolejne kroki

- Przenieś cache z folderu temp do katalogu, który kontrolujesz, i usuwaj pliki starsze niż doba zadaniem cron.
- Dodaj `language=ru-RU` lub inny z [14 języków](https://amodb.dev/pl/languages/) i zapisuj w cache każdy język osobno, co hasz URL już zapewnia.
- Dodaj `moods` do `append_to_response` i wypisz wpis `$movie['moods']['results']` o najwyższym `avg`. Zobacz [nastroje](https://amodb.dev/pl/moods/).
- Przeczytaj o [limitach żądań](https://amodb.dev/pl/rate-limits/) albo porównaj to z [poradnikiem Go](https://amodb.dev/pl/go-movie-api/).

## Pytania i odpowiedzi

### Jakich rozszerzeń PHP potrzebuje kod?

Tylko cURL. Obsługa JSON jest wbudowana w PHP 8. Uruchom php -m, by sprawdzić, czy curl jest na liście. Kod działa na PHP 8.2 i nowszym i nie ma zależności Composera, więc oba pliki możesz wrzucić do dowolnego projektu.

### Gdzie cache plikowy trzyma dane?

W folderze amodb-cache w systemowym katalogu temp. Każde żądanie dostaje jeden plik JSON nazwany od skrótu jego URL. Plik jest używany, dopóki jego wiek jest poniżej TTL, który domyślnie wynosi 6 godzin.

### Jak przekazać klucz pod PHP-FPM?

Dodaj env[AMODB_KEY] do konfiguracji puli, bo workery nie dziedziczą twojego shella. Z wbudowanym serwerem wystarczy zmienna wyeksportowana przed uruchomieniem php -S. Nigdy nie zapisuj klucza w pliku pod katalogiem głównym serwisu.

### Co ma zrobić strona, gdy amodb zwróci błąd?

Pokazać krótki komunikat i wysłać 502, by zauważył to twój własny monitoring. Zapisz status_message w logu na serwerze i nigdy nie wypisuj klucza. 401 oznacza, że każde kolejne wywołanie też zawiedzie, więc popraw klucz przed ponowieniem.

### Czy uśpienie po 429 jest bezpieczne w żądaniu webowym?

Utrzymaj je krótkie. Żądanie, które śpi 30 sekund, blokuje workera, więc ogranicz czekanie na stronach nisko albo podaj starą kopię z cache. Przy cache plikowym powtórzone ID rzadko dochodzą do 429.

## Powiązane strony

- [API szczegółów filmu](https://amodb.dev/pl/movies/): API szczegółów filmu z tymi samymi ścieżkami i JSON co TMDB v3. Pobierz film po ID, dołącz obsadę, oceny i nastroje przez append_to_response, 14 języków.
- [API obrazów i host obrazów](https://amodb.dev/pl/images-api/): API obrazów filmów ze ścieżkami TMDB v3: wypisz plakaty, tła, loga i kadry każdego tytułu, a potem ładuj je z img.amodb.dev w 10 rozmiarach bez klucza.
- [Uwierzytelnianie](https://amodb.dev/pl/authentication/): Jak wysłać klucz amodb: api_key w zapytaniu, nagłówek Authorization Bearer albo apikey dla wywołań OMDb. Format klucza, przechowywanie, rotacja i błędy 401.
- [Błędy](https://amodb.dev/pl/errors/): Każdy błąd amodb ze statusem HTTP, status_code i komunikatem TMDB oraz kształt błędu OMDb. Co oznacza każdy z nich i co powinien zrobić twój klient dalej.
- [Poradnik API filmów w Pythonie](https://amodb.dev/pl/python-movie-api/): Zbuduj w Pythonie z requests narzędzie, które wyszukuje film i wypisuje rok, obsadę, ocenę IMDb i plakat, z ponawianiem po 429 i cache w pliku.
- [Poradnik API filmów w JavaScript](https://amodb.dev/pl/javascript-movie-api/): Napisz skrypt Node.js, który jednym żądaniem na ID pobiera szczegóły, obsadę i oceny filmów, z cache z TTL, ponawianiem po 429 i bez zależności.

Zaktualizowano: 2026-10-18. Source: https://amodb.dev/pl/php-movie-api/
