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.

.php PHP Mis à jour le Voir en Markdown

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. 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 :

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.

Shell
export AMODB_KEY="YOUR_API_KEY"

Étape 2 : préparer le projet

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

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

Shell
mkdir movie-page && cd movie-page
php -v
php -m | grep -i 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, et les tailles d'affiches sur images.

É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.

StatutCe que fait la fonction
401Lève aussitôt. Une clé mauvaise ou suspendue ne se corrige pas en réessayant.
404Lève avec le status_message, pour que la page puisse dire que l'identifiant est inconnu.
429Lit l'en-tête Retry-After, attend ce nombre de secondes (de 1 à 30), puis réessaie jusqu'à deux fois.
5xxLève avec le code de statut et le message.
Erreur cURLLè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 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.

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é.

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>

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 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.
  • Lisez les limites de débit, ou comparez avec le tutoriel Go.