# Tutorial API filme PHP

> Construiești o funcție PHP reutilizabilă și o pagină de film mică. Funcția apelează amodb cu cURL, citește Retry-After la un 429 și ține fiecare răspuns în cache într-un fișier timp de 6 ore. Pagina arată posterul, distribuția și scorul IMDb. Cere PHP 8.2 sau mai nou cu extensia curl și durează cam 20 de minute.

Vei construi două fișiere mici. `amodb.php` conține o singură funcție, `amodb_get()`, care apelează API-ul cu cURL și ține răspunsurile în cache în fișiere. `movie.php` este o pagină care arată un film după ID, cu poster, distribuție și scorul IMDb. Ai nevoie de PHP 8.2 sau mai nou cu extensia `curl` și de cam 20 de minute.

## Pasul 1: obține o cheie API

Cumpără o perioadă plătită în avans la [prețuri](https://amodb.dev/ro/pricing/). Nu există nivel gratuit, iar fiecare plan întoarce setul complet de date la 20, 40 sau 120 de cereri pe minut. Creează o cheie în panoul contului tău. Are 32 de caractere hexazecimale minuscule. Exportă-o înainte să pornești PHP:

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

Codul o citește cu `getenv('AMODB_KEY')`. Sub PHP-FPM, worker-ii nu văd shell-ul tău, deci setează în schimb `env[AMODB_KEY]` în configurația pool-ului.

## Pasul 2: pregătește proiectul

Fă un folder și verifică dacă cURL este disponibil:

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

Prima comandă ar trebui să raporteze 8.2 sau mai mult. A doua trebuie să afișeze `curl`.

## Pasul 3: trimite prima cerere

Un apel cURL simplu are nevoie de patru lucruri: URL-ul, un timeout, antetul `Authorization` și o verificare a stării. În PHP 8 handle-ul se eliberează singur, deci nu există `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;
```

## Pasul 4: învelește-l într-o funcție

Apelarea cURL din fiecare pagină repetă aceeași configurare. `amodb_get($path, $query, $ttl)` construiește URL-ul cu `http_build_query`, trimite apelul și întoarce un array decodat. Pagina de film transmite `append_to_response=credits,ratings`, deci distribuția și scorurile sosesc în aceeași cerere. Detaliile câmpurilor sunt la [filme](https://amodb.dev/ro/movies/), iar dimensiunile posterelor la [imagini](https://amodb.dev/ro/images-api/).

## Pasul 5: tratează erorile și limitele de cereri

Funcția aruncă `AmodbException` la fiecare eșec, iar pagina o prinde într-un singur loc.

| Stare       | Ce face funcția                                                                                            |
| ----------- | ---------------------------------------------------------------------------------------------------------- |
| 401         | Aruncă imediat. O cheie greșită sau suspendată nu se repară prin reîncercare.                              |
| 404         | Aruncă cu `status_message`, ca pagina să poată spune că ID-ul este necunoscut.                             |
| 429         | Citește antetul `Retry-After`, doarme atâtea secunde (de la 1 la 30), apoi reîncearcă de până la două ori. |
| 5xx         | Aruncă cu codul de stare și mesajul.                                                                       |
| Eroare cURL | Aruncă `Network error` cu textul din `curl_error()`.                                                       |

Antetul sosește prin `CURLOPT_HEADERFUNCTION`, un callback pe care cURL îl rulează pentru fiecare linie de antet din răspuns. Vezi [erori](https://amodb.dev/ro/errors/) pentru lista completă de coduri.

## Pasul 6: păstrează răspunsurile în fișiere

Marginea ține detaliile 6 ore. Un fișier local economisește cererea în sine, iar o lovitură de cache nu costă nimic din limita ta. Vârstele se adună, deci un fișier vechi de 6 ore poate conține date care erau deja vechi de ore la margine. Scade TTL dacă asta contează. Funcția face hash `sha1` pe URL-ul final, caută un fișier cu acel nume și îl întoarce când timpul lui de modificare este mai nou decât TTL. Altfel preia, scrie corpul cu `LOCK_EX` și întoarce datele.

## Pasul 7: programul complet

Salvează funcția ca `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);
}
```

Apoi salvează pagina ca `movie.php`. Fiecare valoare trece prin `htmlspecialchars` înainte să ajungă în 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>
```

Pornește serverul încorporat cu `php -S localhost:8000` și deschide `http://localhost:8000/movie.php?id=603`. URL-ul posterului indică gazda de imagini, care nu cere cheie.

## Pașii următori

- Mută cache-ul din folderul temporar într-un director pe care îl controlezi și șterge fișierele mai vechi de o zi cu un job cron.
- Adaugă `language=ru-RU` sau alta dintre cele [14 limbi](https://amodb.dev/ro/languages/) și pune în cache fiecare limbă separat, lucru pe care hash-ul URL-ului îl face deja.
- Adaugă `moods` la `append_to_response` și afișează intrarea din `$movie['moods']['results']` cu cel mai mare `avg`. Vezi [atmosfere](https://amodb.dev/ro/moods/).
- Citește despre [limite de cereri](https://amodb.dev/ro/rate-limits/) sau compară cu [tutorialul Go](https://amodb.dev/ro/go-movie-api/).

## Întrebări și răspunsuri

### De ce extensii PHP are nevoie codul?

Doar cURL. Suportul JSON este încorporat în PHP 8. Rulează php -m ca să verifici că apare curl. Codul rulează pe PHP 8.2 și mai nou și nu are dependențe Composer, deci poți pune cele două fișiere în orice proiect.

### Unde își ține cache-ul în fișiere datele?

Într-un folder numit amodb-cache în directorul temporar al sistemului. Fiecare cerere primește un fișier JSON denumit după un hash al URL-ului ei. Un fișier este reutilizat cât timp vârsta lui este sub TTL, care este implicit de 6 ore.

### Cum transmit cheia sub PHP-FPM?

Adaugă env[AMODB_KEY] în configurația pool-ului, pentru că worker-ii nu moștenesc shell-ul tău. Cu serverul încorporat, variabila exportată înainte să rulezi php -S este suficientă. Nu scrie niciodată cheia într-un fișier aflat sub rădăcina web.

### Ce ar trebui să facă pagina când amodb întoarce o eroare?

Arată un mesaj scurt și trimite un 502, ca monitorizarea ta să observe. Loghează status_message pe server și nu afișa niciodată cheia. Un 401 înseamnă că și toate apelurile următoare vor eșua, deci repară cheia înainte să reîncerci.

### Este sigur să dormi la un 429 în interiorul unei cereri web?

Ține-l scurt. O cerere care doarme 30 de secunde blochează un worker, așa că plafonează așteptarea jos pe pagini sau servește o copie veche din cache. Cu cache-ul în fișiere activ, ID-urile repetate ajung rar la un 429.

## Pagini conexe

- [API detalii film](https://amodb.dev/ro/movies/): API detalii film cu aceleași căi și același JSON ca TMDB v3. Preia un film după ID, adaugă credite, evaluări și atmosferă cu append_to_response, în 14 limbi.
- [API imagini și gazdă de imagini](https://amodb.dev/ro/images-api/): API imagini filme cu căi TMDB v3: listează postere, imagini de fundal, logouri și cadre, apoi încarcă-le de la img.amodb.dev în 10 dimensiuni, fără cheie.
- [Autentificare](https://amodb.dev/ro/authentication/): Cum trimiți cheia amodb: api_key în interogare, antetul Authorization Bearer sau apikey pentru apelurile OMDb. Format, stocare, rotire și erorile 401.
- [Erori](https://amodb.dev/ro/errors/): Fiecare eroare amodb, cu statusul HTTP, status_code TMDB și mesajul, plus forma erorii OMDb. Ce înseamnă fiecare și ce ar trebui să facă clientul tău.
- [Tutorial API filme Python](https://amodb.dev/ro/python-movie-api/): Instrument Python în linia de comandă care caută un film și afișează anul, distribuția, evaluarea IMDb și posterul, cu reîncercare la 429 și cache în fișier.
- [Tutorial API filme JavaScript](https://amodb.dev/ro/javascript-movie-api/): Scrie un script Node.js care încarcă detalii, credite și evaluări pentru o listă de filme, o cerere pe film, cu cache TTL, reîncercare la 429, fără dependențe.

Actualizat: 2026-10-18. Source: https://amodb.dev/ro/php-movie-api/
