# Návod na filmové API v PHP

> Vytvořte jednu znovupoužitelnou funkci v PHP a malou stránku filmu. Funkce volá amodb přes cURL, při 429 čte Retry-After a každou odpověď cachuje v souboru na 6 hodin. Stránka ukazuje plakát, herce a skóre IMDb. Vyžaduje PHP 8.2 nebo novější s rozšířením curl a zabere asi 20 minut.

Vytvoříte dva malé soubory. `amodb.php` obsahuje jednu funkci, `amodb_get()`, která volá API přes cURL a cachuje odpovědi v souborech. `movie.php` je stránka, která podle ID zobrazí film s plakátem, herci a skóre IMDb. Potřebujete PHP 8.2 nebo novější s rozšířením `curl` a asi 20 minut.

## Krok 1: Získejte API klíč

Zakupte si předplacené období na stránce [ceník](https://amodb.dev/cs/pricing/). Bezplatný tarif neexistuje a každý tarif vrací celou datovou sadu při 20, 40 nebo 120 požadavcích za minutu. Vytvořte klíč v panelu účtu. Je to 32 malých hexadecimálních znaků. Exportujte ho před spuštěním PHP:

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

Kód ho čte pomocí `getenv('AMODB_KEY')`. Pod PHP-FPM workery váš shell nevidí, nastavte proto místo toho `env[AMODB_KEY]` v konfiguraci poolu.

## Krok 2: Připravte projekt

Vytvořte složku a ověřte, že je cURL k dispozici:

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

První příkaz by měl hlásit 8.2 nebo vyšší. Druhý musí vypsat `curl`.

## Krok 3: Odešlete první požadavek

Holé volání cURL potřebuje čtyři věci: URL, timeout, hlavičku `Authorization` a kontrolu stavu. V PHP 8 se handle uvolní sám, takže `curl_close()` není potřeba.

```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: Zabalte to do funkce

Volání cURL z každé stránky opakuje stejné nastavení. `amodb_get($path, $query, $ttl)` sestaví URL pomocí `http_build_query`, odešle volání a vrátí dekódované pole. Stránka filmu předává `append_to_response=credits,ratings`, takže herci a skóre přijdou ve stejném požadavku. Podrobnosti polí jsou na stránce [filmy](https://amodb.dev/cs/movies/) a velikosti plakátů na stránce [obrázky](https://amodb.dev/cs/images-api/).

## Krok 5: Ošetřete chyby a limity požadavků

Funkce při každém selhání vyhazuje `AmodbException` a stránka ji zachytí na jednom místě.

| Stav       | Co dělá funkce                                                                             |
| ---------- | ------------------------------------------------------------------------------------------ |
| 401        | Vyhodí výjimku hned. Špatný nebo pozastavený klíč opakováním nespravíte.                   |
| 404        | Vyhodí výjimku se `status_message`, aby stránka mohla říct, že ID není známé.              |
| 429        | Přečte hlavičku `Retry-After`, počká tolik sekund (1 až 30) a pak opakuje nejvýše dvakrát. |
| 5xx        | Vyhodí výjimku se stavovým kódem a zprávou.                                                |
| Chyba cURL | Vyhodí `Network error` s textem z `curl_error()`.                                          |

Hlavička přichází přes `CURLOPT_HEADERFUNCTION`, callback, který cURL spouští pro každý řádek hlavičky odpovědi. Úplný seznam kódů najdete na stránce [chyby](https://amodb.dev/cs/errors/).

## Krok 6: Ukládejte odpovědi do souborů

Edge drží detaily 6 hodin. Lokální soubor ušetří samotný požadavek a zásah cache nestojí nic z vašeho limitu. Stáří se sčítá, takže soubor starý 6 hodin může obsahovat data, která byla na edge už hodiny stará. Pokud na tom záleží, snižte TTL. Funkce zahashuje konečné URL pomocí `sha1`, vyhledá soubor s tímto názvem a vrátí ho, když je jeho čas změny novější než TTL. Jinak načte data, zapíše tělo s `LOCK_EX` a vrátí data.

## Krok 7: Kompletní program

Uložte funkci 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);
}
```

Pak uložte stránku jako `movie.php`. Každá hodnota projde přes `htmlspecialchars`, než se dostane 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>
```

Spusťte vestavěný server pomocí `php -S localhost:8000` a otevřete `http://localhost:8000/movie.php?id=603`. URL plakátu míří na hostitele obrázků, který klíč nepotřebuje.

## Další kroky

- Přesuňte cache ze složky temp do adresáře, který ovládáte, a soubory starší než den mažte úlohou cron.
- Přidejte `language=ru-RU` nebo jiný z [14 jazyků](https://amodb.dev/cs/languages/) a cachujte každý jazyk zvlášť, což hash URL už zajišťuje.
- Přidejte `moods` do `append_to_response` a vypište položku `$movie['moods']['results']` s nejvyšším `avg`. Viz [nálady](https://amodb.dev/cs/moods/).
- Přečtěte si o [limitech požadavků](https://amodb.dev/cs/rate-limits/) nebo to porovnejte s [návodem pro Go](https://amodb.dev/cs/go-movie-api/).

## Otázky a odpovědi

### Která rozšíření PHP kód potřebuje?

Jen cURL. Podpora JSON je v PHP 8 vestavěná. Spusťte php -m a ověřte, že je curl v seznamu. Kód běží na PHP 8.2 a novějším a nemá žádné závislosti Composeru, takže oba soubory můžete vložit do libovolného projektu.

### Kde souborová cache drží svá data?

Ve složce amodb-cache uvnitř systémového adresáře temp. Každý požadavek dostane jeden soubor JSON pojmenovaný podle hashe svého URL. Soubor se používá, dokud je jeho stáří pod TTL, což je ve výchozím stavu 6 hodin.

### Jak předám klíč pod PHP-FPM?

Přidejte env[AMODB_KEY] do konfigurace poolu, protože workery nedědí váš shell. S vestavěným serverem stačí proměnná, kterou jste exportovali před spuštěním php -S. Nikdy nezapisujte klíč do souboru pod webovým kořenem.

### Co má stránka udělat, když amodb vrátí chybu?

Ukázat krátkou zprávu a poslat 502, aby si toho všimlo vaše vlastní monitorování. Zapište status_message do logu na serveru a klíč nikdy nevypisujte. 401 znamená, že selže i každé další volání, opravte tedy klíč před dalším pokusem.

### Je uspání po 429 bezpečné uvnitř webového požadavku?

Držte ho krátké. Požadavek, který spí 30 sekund, blokuje workera, proto omezte čekání na stránkách nízko, nebo servírujte starou cachovanou kopii. S souborovou cache dosáhnou opakovaná ID 429 jen zřídka.

## Související stránky

- [API detailu filmu](https://amodb.dev/cs/movies/): API detailu filmu se stejnými cestami a JSON jako TMDB v3. Načtěte film podle ID, přidejte obsazení, hodnocení a nálady přes append_to_response, 14 jazyků.
- [API obrázků a hostitel obrázků](https://amodb.dev/cs/images-api/): API obrázků filmů s cestami TMDB v3: vypište plakáty, pozadí, loga a záběry každého titulu a načtěte je z img.amodb.dev v 10 velikostech bez klíče.
- [Autentizace](https://amodb.dev/cs/authentication/): Jak poslat klíč amodb: api_key v dotazu, hlavička Authorization Bearer nebo apikey pro volání OMDb. Formát klíče, uložení, rotace a chyby 401.
- [Chyby](https://amodb.dev/cs/errors/): Každá chyba amodb se stavem HTTP, status_code a zprávou TMDB a také tvar chyby OMDb. Co která znamená a co má váš klient udělat v dalším kroku.
- [Návod na filmové API v Pythonu](https://amodb.dev/cs/python-movie-api/): Vytvořte v Pythonu s knihovnou requests nástroj, který vyhledá film a vypíše rok, herce, hodnocení IMDb a plakát, s opakováním při 429 a souborovou cache.
- [Návod na filmové API v JavaScriptu](https://amodb.dev/cs/javascript-movie-api/): Napište skript v Node.js, který jedním požadavkem na ID načte detaily, obsazení a hodnocení filmů, s cache s TTL, opakováním při 429 a bez závislostí.

Aktualizováno: 2026-10-18. Source: https://amodb.dev/cs/php-movie-api/
