# Tutoriel API de films en PHP

> Créez une fonction PHP réutilisable et une petite page de film. La fonction appelle amodb avec cURL, lit Retry-After sur un 429 et met chaque réponse en cache dans un fichier pendant 6 heures. La page affiche l'affiche, la distribution et la note IMDb. Il faut PHP 8.2 ou plus récent avec l'extension curl et environ 20 minutes.

Vous allez construire deux petits fichiers. `amodb.php` contient une fonction, `amodb_get()`, qui appelle l'API avec cURL et met les réponses en cache dans des fichiers. `movie.php` est une page qui montre un film par identifiant avec l'affiche, la distribution et la note IMDb. Il vous faut PHP 8.2 ou plus récent avec l'extension `curl` et environ 20 minutes.

## Étape 1 : obtenir une clé d'API

Achetez une période prépayée sur les [tarifs](https://amodb.dev/fr/pricing/). Il n'y a pas d'offre gratuite, et chaque offre renvoie le jeu de données complet à 20, 40 ou 120 requêtes par minute. Créez une clé dans le tableau de bord de votre compte. Elle fait 32 caractères hexadécimaux en minuscules. Exportez-la avant de lancer PHP :

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

Le code la lit avec `getenv('AMODB_KEY')`. Sous PHP-FPM, les workers ne voient pas votre shell : définissez plutôt `env[AMODB_KEY]` dans la configuration du pool.

## Étape 2 : préparer le projet

Créez un dossier et vérifiez que cURL est disponible :

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

La première commande doit indiquer 8.2 ou plus. La seconde doit afficher `curl`.

## Étape 3 : envoyer la première requête

Un appel cURL nu demande quatre choses : l'URL, un délai d'expiration, l'en-tête `Authorization` et une vérification du statut. En PHP 8, le handle se libère tout seul : il n'y a donc pas de `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;
```

## Étape 4 : l'envelopper dans une fonction

Appeler cURL depuis chaque page répète la même configuration. `amodb_get($path, $query, $ttl)` construit l'URL avec `http_build_query`, envoie l'appel et renvoie un tableau décodé. La page de film passe `append_to_response=credits,ratings`, si bien que la distribution et les notes arrivent dans la même requête. Les détails des champs sont sur [films](https://amodb.dev/fr/movies/), et les tailles d'affiches sur [images](https://amodb.dev/fr/images-api/).

## Étape 5 : gérer les erreurs et les limites de débit

La fonction lève `AmodbException` pour chaque échec, et la page l'intercepte à un seul endroit.

| Statut      | Ce que fait la fonction                                                                                 |
| ----------- | ------------------------------------------------------------------------------------------------------- |
| 401         | Lève aussitôt. Une clé mauvaise ou suspendue ne se corrige pas en réessayant.                           |
| 404         | Lève avec le `status_message`, pour que la page puisse dire que l'identifiant est inconnu.              |
| 429         | Lit l'en-tête `Retry-After`, attend ce nombre de secondes (de 1 à 30), puis réessaie jusqu'à deux fois. |
| 5xx         | Lève avec le code de statut et le message.                                                              |
| Erreur cURL | Lève `Network error` avec le texte de `curl_error()`.                                                   |

L'en-tête arrive via `CURLOPT_HEADERFUNCTION`, un rappel que cURL exécute pour chaque ligne d'en-tête de la réponse. Voir [erreurs](https://amodb.dev/fr/errors/) pour la liste complète des codes.

## Étape 6 : mettre les réponses en cache dans des fichiers

La périphérie garde les détails 6 heures. Un fichier local économise la requête elle-même, et un succès du cache ne coûte rien sur votre limite. Les âges s'additionnent : un fichier de 6 heures peut donc contenir des données qui avaient déjà des heures à la périphérie. Baissez la durée de vie si cela compte. La fonction calcule le hachage `sha1` de l'URL finale, cherche un fichier de ce nom et le renvoie si sa date de modification est plus récente que la durée de vie. Sinon elle récupère les données, écrit le corps avec `LOCK_EX` et renvoie les données.

## Étape 7 : le programme complet

Enregistrez la fonction sous `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);
}
```

Enregistrez ensuite la page sous `movie.php`. Chaque valeur passe par `htmlspecialchars` avant d'arriver dans le 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>
```

Démarrez le serveur intégré avec `php -S localhost:8000` et ouvrez `http://localhost:8000/movie.php?id=603`. L'URL de l'affiche pointe vers l'hôte d'images, qui n'exige aucune clé.

## Pour aller plus loin

- Déplacez le cache du dossier temporaire vers un répertoire que vous contrôlez, et supprimez les fichiers de plus d'un jour avec une tâche cron.
- Ajoutez `language=ru-RU` ou une autre des [14 langues](https://amodb.dev/fr/languages/) et mettez chaque langue en cache séparément, ce que fait déjà le hachage de l'URL.
- Ajoutez `moods` à `append_to_response` et affichez l'entrée de `$movie['moods']['results']` ayant le `avg` le plus élevé. Voir [ambiances](https://amodb.dev/fr/moods/).
- Lisez les [limites de débit](https://amodb.dev/fr/rate-limits/), ou comparez avec le [tutoriel Go](https://amodb.dev/fr/go-movie-api/).

## Questions et réponses

### De quelles extensions PHP le code a-t-il besoin ?

Seulement de cURL. La prise en charge du JSON est intégrée à PHP 8. Lancez php -m pour vérifier que curl figure dans la liste. Le code fonctionne sur PHP 8.2 et plus récent et n'a aucune dépendance Composer : vous pouvez donc déposer les deux fichiers dans n'importe quel projet.

### Où le cache de fichiers garde-t-il ses données ?

Dans un dossier nommé amodb-cache à l'intérieur du répertoire temporaire du système. Chaque requête obtient un fichier JSON nommé d'après un hachage de son URL. Un fichier est réutilisé tant que son âge est inférieur à la durée de vie, qui est de 6 heures par défaut.

### Comment passer la clé sous PHP-FPM ?

Ajoutez env[AMODB_KEY] à la configuration du pool, car les workers n'héritent pas de votre shell. Avec le serveur intégré, la variable que vous avez exportée avant de lancer php -S suffit. N'écrivez jamais la clé dans un fichier sous la racine web.

### Que doit faire la page quand amodb renvoie une erreur ?

Afficher un court message et envoyer un 502 pour que votre propre supervision le remarque. Consignez le status_message sur le serveur et n'affichez jamais la clé. Un 401 signifie que tous les appels suivants échoueront aussi : corrigez la clé avant de réessayer.

### Attendre sur un 429 est-il sûr dans une requête web ?

Gardez-le court. Une requête qui attend 30 secondes immobilise un worker : plafonnez donc l'attente à une valeur basse sur les pages, ou servez une copie en cache périmée. Avec le cache de fichiers en place, les identifiants répétés atteignent rarement un 429.

## Pages liées

- [API de détails de film](https://amodb.dev/fr/movies/): API de détails de film aux chemins et au JSON de TMDB v3. Obtenez un film par id, ajoutez crédits, notes et ambiances avec append_to_response, en 14 langues.
- [API d'images et hôte d'images](https://amodb.dev/fr/images-api/): API d'images de films aux chemins TMDB v3 : listez affiches, fonds, logos et images par titre, puis chargez-les depuis img.amodb.dev en 10 tailles sans clé.
- [Authentification](https://amodb.dev/fr/authentication/): Comment envoyer votre clé amodb : api_key dans la requête, en-tête Authorization Bearer, ou apikey pour OMDb. Format, stockage, rotation et erreurs 401.
- [Erreurs](https://amodb.dev/fr/errors/): Chaque erreur amodb : statut HTTP, status_code et message TMDB, plus la forme d'erreur OMDb. Ce que chacune signifie et ce que votre client doit faire.
- [Tutoriel API de films en Python](https://amodb.dev/fr/python-movie-api/): Un outil Python en CLI qui cherche un film et affiche année, distribution, note IMDb et affiche, avec relance 429 et cache fichier. Utilise requests.
- [Tutoriel API de films JavaScript](https://amodb.dev/fr/javascript-movie-api/): Un script Node.js qui charge détails, crédits et notes d'une liste d'ids de films en une requête chacun, avec cache TTL, relance 429 et sans dépendance.

Mis à jour: 2026-10-18. Source: https://amodb.dev/fr/php-movie-api/
