# Tutorial API film in PHP

> Crea una funzione PHP riutilizzabile e una piccola pagina film. La funzione chiama amodb con cURL, legge Retry-After su un 429 e mette in cache ogni risposta in un file per 6 ore. La pagina mostra locandina, cast e punteggio IMDb. Servono PHP 8.2 o successivo con l'estensione curl e circa 20 minuti.

Costruirai due piccoli file. `amodb.php` contiene una funzione, `amodb_get()`, che chiama l'API con cURL e mette in cache le risposte in file. `movie.php` è una pagina che mostra un film per ID con locandina, cast e punteggio IMDb. Ti servono PHP 8.2 o successivo con l'estensione `curl` e circa 20 minuti.

## Passaggio 1: ottieni una chiave API

Acquista un periodo prepagato nella pagina dei [prezzi](https://amodb.dev/it/pricing/). Non esiste un piano gratuito, e ogni piano restituisce il dataset completo a 20, 40 o 120 richieste al minuto. Crea una chiave nella dashboard del tuo account. È di 32 caratteri esadecimali minuscoli. Esportala prima di avviare PHP:

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

Il codice la legge con `getenv('AMODB_KEY')`. Sotto PHP-FPM i worker non vedono la tua shell, quindi imposta invece `env[AMODB_KEY]` nella configurazione del pool.

## Passaggio 2: prepara il progetto

Crea una cartella e verifica che cURL sia disponibile:

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

Il primo comando dovrebbe riportare 8.2 o superiore. Il secondo deve stampare `curl`.

## Passaggio 3: invia la prima richiesta

Una chiamata cURL minima richiede quattro cose: l'URL, un timeout, l'header `Authorization` e un controllo dello stato. In PHP 8 l'handle si libera da solo, quindi non c'è `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;
```

## Passaggio 4: avvolgila in una funzione

Chiamare cURL da ogni pagina ripete la stessa configurazione. `amodb_get($path, $query, $ttl)` costruisce l'URL con `http_build_query`, invia la chiamata e restituisce un array decodificato. La pagina del film passa `append_to_response=credits,ratings`, così cast e punteggi arrivano nella stessa richiesta. I dettagli dei campi sono nella pagina [film](https://amodb.dev/it/movies/), e le dimensioni delle locandine nella pagina [immagini](https://amodb.dev/it/images-api/).

## Passaggio 5: gestisci errori e limiti di richieste

La funzione solleva `AmodbException` per ogni errore, e la pagina la intercetta in un solo punto.

| Stato       | Cosa fa la funzione                                                                                       |
| ----------- | --------------------------------------------------------------------------------------------------------- |
| 401         | Solleva subito. Una chiave sbagliata o sospesa non si corregge riprovando.                                |
| 404         | Solleva con lo `status_message`, così la pagina può dire che l'ID è sconosciuto.                          |
| 429         | Legge l'header `Retry-After`, dorme per quel numero di secondi (da 1 a 30), poi ritenta fino a due volte. |
| 5xx         | Solleva con il codice di stato e il messaggio.                                                            |
| Errore cURL | Solleva `Network error` con il testo di `curl_error()`.                                                   |

L'header arriva tramite `CURLOPT_HEADERFUNCTION`, un callback che cURL esegue per ogni riga di header della risposta. Vedi [errori](https://amodb.dev/it/errors/) per l'elenco completo dei codici.

## Passaggio 6: metti le risposte in cache nei file

L'edge tiene i dettagli per 6 ore. Un file locale risparmia la richiesta stessa, e un cache hit non costa nulla sul tuo limite. Le età si sommano, quindi un file di 6 ore può contenere dati che erano già vecchi di ore sull'edge. Abbassa il TTL se ti importa. La funzione calcola l'hash `sha1` dell'URL finale, cerca un file con quel nome e lo restituisce se la sua data di modifica è più recente del TTL. Altrimenti recupera i dati, scrive il corpo con `LOCK_EX` e restituisce i dati.

## Passaggio 7: il programma completo

Salva la funzione come `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);
}
```

Poi salva la pagina come `movie.php`. Ogni valore passa da `htmlspecialchars` prima di arrivare nell'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>
```

Avvia il server integrato con `php -S localhost:8000` e apri `http://localhost:8000/movie.php?id=603`. L'URL della locandina punta all'host delle immagini, che non richiede alcuna chiave.

## Prossimi passi

- Sposta la cache dalla cartella temporanea a una directory che controlli, ed elimina i file più vecchi di un giorno con un job cron.
- Aggiungi `language=ru-RU` o un'altra tra le [14 lingue](https://amodb.dev/it/languages/) e metti in cache ogni lingua separatamente, cosa che l'hash dell'URL già fa.
- Aggiungi `moods` a `append_to_response` e stampa la voce di `$movie['moods']['results']` con l'`avg` più alto. Vedi [mood](https://amodb.dev/it/moods/).
- Leggi i [limiti di richieste](https://amodb.dev/it/rate-limits/), oppure confronta con il [tutorial Go](https://amodb.dev/it/go-movie-api/).

## Domande e risposte

### Di quali estensioni PHP ha bisogno il codice?

Solo di cURL. Il supporto JSON è integrato in PHP 8. Esegui php -m per verificare che curl sia nell'elenco. Il codice gira su PHP 8.2 e successivi e non ha dipendenze Composer, quindi puoi mettere i due file in qualsiasi progetto.

### Dove tiene i dati la cache su file?

In una cartella chiamata amodb-cache dentro la directory temporanea del sistema. Ogni richiesta ottiene un file JSON nominato da un hash del suo URL. Un file viene riusato finché la sua età è inferiore al TTL, che per impostazione predefinita è di 6 ore.

### Come passo la chiave sotto PHP-FPM?

Aggiungi env[AMODB_KEY] alla configurazione del pool, perché i worker non ereditano la tua shell. Con il server integrato basta la variabile che hai esportato prima di eseguire php -S. Non scrivere mai la chiave in un file sotto la radice web.

### Cosa deve fare la pagina quando amodb restituisce un errore?

Mostrare un breve messaggio e inviare un 502 così il tuo monitoraggio se ne accorge. Registra lo status_message sul server e non stampare mai la chiave. Un 401 significa che anche tutte le chiamate successive falliranno, quindi correggi la chiave prima di riprovare.

### Dormire su un 429 è sicuro dentro una richiesta web?

Tienilo breve. Una richiesta che dorme per 30 secondi occupa un worker, quindi limita l'attesa a un valore basso sulle pagine, oppure servi una copia in cache non aggiornata. Con la cache su file attiva, gli ID ripetuti raramente arrivano a un 429.

## Pagine correlate

- [API dei dettagli del film](https://amodb.dev/it/movies/): API dettagli film con gli stessi percorsi e JSON di TMDB v3. Ottieni un film per ID, aggiungi crediti, valutazioni e mood con append_to_response, in 14 lingue.
- [API delle immagini e host delle immagini](https://amodb.dev/it/images-api/): API immagini dei film con percorsi TMDB v3: elenca locandine, sfondi, loghi e fermi immagine di ogni titolo e caricali da img.amodb.dev in 10 dimensioni.
- [Autenticazione](https://amodb.dev/it/authentication/): Come inviare la chiave amodb: api_key nella query, un header Authorization Bearer o apikey per le chiamate OMDb. Formato, conservazione, rotazione, errori 401.
- [Errori](https://amodb.dev/it/errors/): Ogni errore di amodb con stato HTTP, status_code e messaggio TMDB, più la forma dell'errore OMDb. Cosa significa ciascuno e cosa deve fare il tuo client.
- [Tutorial API film in Python](https://amodb.dev/it/python-movie-api/): Un tool Python da riga di comando che cerca un film e stampa anno, cast, valutazione IMDb e locandina, con retry su 429 e cache su file. Usa requests.
- [Tutorial API film JavaScript](https://amodb.dev/it/javascript-movie-api/): Uno script Node.js che carica dettagli, crediti e valutazioni di un elenco di ID film con una richiesta ciascuno, cache TTL, retry su 429 e zero dipendenze.

Aggiornato: 2026-10-18. Source: https://amodb.dev/it/php-movie-api/
