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.

.php PHP Actualizat Vezi ca Markdown

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

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.

Shell
export AMODB_KEY="YOUR_API_KEY"

Pasul 2: pregătește proiectul

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

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

Shell
mkdir movie-page && cd movie-page
php -v
php -m | grep -i 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, iar dimensiunile posterelor la imagini.

Pasul 5: tratează erorile și limitele de cereri

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

StareCe face funcția
401Aruncă imediat. O cheie greșită sau suspendată nu se repară prin reîncercare.
404Aruncă cu status_message, ca pagina să poată spune că ID-ul este necunoscut.
429Citește antetul Retry-After, doarme atâtea secunde (de la 1 la 30), apoi reîncearcă de până la două ori.
5xxAruncă cu codul de stare și mesajul.
Eroare cURLAruncă 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 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.

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.

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>

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 ș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.
  • Citește despre limite de cereri sau compară cu tutorialul Go.