מדריך API לסרטים ב-PHP

בנו פונקציית PHP אחת לשימוש חוזר ועמוד סרט קטן. הפונקציה קוראת ל-amodb עם cURL, קוראת את Retry-After ב-429 ושומרת כל תגובה בקובץ ל-6 שעות. העמוד מציג פוסטר, שחקנים וציון IMDb, וכל ערך עובר htmlspecialchars לפני ההצגה. נדרשים PHP 8.2 ומעלה עם תוסף curl וכ-20 דקות.

.php PHP עודכן צפייה כ-Markdown

בונים שני קבצים קטנים. amodb.php מחזיק פונקציה אחת, amodb_get(), שקוראת ל-API עם cURL ושומרת תגובות בקבצים. movie.php הוא עמוד שמציג סרט לפי מזהה עם פוסטר, שחקנים וציון IMDb. צריך PHP 8.2 ומעלה עם התוסף curl וכ-20 דקות.

שלב 1: קבלת מפתח API

קנו תקופה בתשלום מראש בעמוד תמחור. אין מסלול חינמי, וכל מסלול מחזיר את מערך הנתונים המלא ב-20, 40 או 120 בקשות לדקה. צרו מפתח בלוח הבקרה של החשבון. הוא 32 תווים הקסדצימליים באותיות קטנות. ייצאו אותו לפני שמפעילים את PHP:

הקוד קורא אותו עם getenv('AMODB_KEY'). ב-PHP-FPM תהליכי העבודה לא רואים את ה-shell שלכם, ולכן הגדירו במקום זה env[AMODB_KEY] בהגדרות ה-pool.

Shell
export AMODB_KEY="YOUR_API_KEY"

שלב 2: הכנת הפרויקט

צרו תיקייה ובדקו ש-cURL זמין:

הפקודה הראשונה אמורה לדווח על 8.2 ומעלה. השנייה חייבת להדפיס curl.

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

שלב 3: שליחת הבקשה הראשונה

קריאת cURL חשופה צריכה ארבעה דברים: הכתובת, פסק זמן, הכותרת Authorization ובדיקת סטטוס. ב-PHP 8 הידית משתחררת מעצמה, ולכן אין 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;

שלב 4: עטיפה בפונקציה

קריאה ל-cURL מכל עמוד חוזרת על אותה הגדרה. amodb_get($path, $query, $ttl) בונה את הכתובת עם http_build_query, שולחת את הקריאה ומחזירה מערך מפוענח. עמוד הסרט מעביר append_to_response=credits,ratings, ולכן שחקנים וציונים מגיעים באותה בקשה. פרטי השדות בעמוד סרטים, וגדלי הפוסטרים בעמוד תמונות.

שלב 5: טיפול בשגיאות ובמגבלות קצב

הפונקציה זורקת AmodbException בכל כשל, והעמוד תופס אותה במקום אחד.

סטטוסמה הפונקציה עושה
401זורקת מיד. מפתח שגוי או מושעה אי אפשר לתקן בניסיון חוזר.
404זורקת עם status_message, כדי שהעמוד יוכל לומר שהמזהה לא מוכר.
429קוראת את הכותרת Retry-After, ישנה כמספר השניות הזה (1 עד 30) ומנסה שוב עד פעמיים.
5xxזורקת עם קוד הסטטוס וההודעה.
שגיאת cURLזורקת Network error עם הטקסט מ-curl_error().

הכותרת מגיעה דרך CURLOPT_HEADERFUNCTION, פונקציית קריאה חוזרת ש-cURL מריץ לכל שורת כותרת בתגובה. את רשימת הקודים המלאה ראו בעמוד שגיאות.

שלב 6: שמירת תגובות בקבצים

הקצה שומר פרטים 6 שעות. קובץ מקומי חוסך את הבקשה עצמה, ופגיעה במטמון לא עולה דבר מהמגבלה שלכם. הגילאים מצטברים, ולכן קובץ בן 6 שעות יכול להחזיק נתונים שכבר היו בני שעות בקצה. הנמיכו את ה-TTL אם זה חשוב. הפונקציה מגבבת את הכתובת הסופית עם sha1, מחפשת קובץ בשם הזה ומחזירה אותו כשזמן השינוי שלו חדש מה-TTL. אחרת היא שולפת, כותבת את הגוף עם LOCK_EX ומחזירה את הנתונים.

שלב 7: התוכנית המלאה

שמרו את הפונקציה כ-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);
}

אחר כך שמרו את העמוד כ-movie.php. כל ערך עובר דרך htmlspecialchars לפני שהוא מגיע ל-HTML.

הפעילו את השרת המובנה עם php -S localhost:8000 ופתחו את http://localhost:8000/movie.php?id=603. כתובת הפוסטר מצביעה על מארח התמונות, שלא דורש מפתח.

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>

הצעדים הבאים

  • העבירו את המטמון מתיקיית הזמניים לתיקייה שבשליטתכם, ומחקו קבצים ישנים מיום באמצעות משימת cron.
  • הוסיפו language=ru-RU או אחת מ-14 השפות האחרות ושמרו כל שפה במטמון בנפרד, מה שגיבוב הכתובת כבר עושה.
  • הוסיפו moods ל-append_to_response והדפיסו את הפריט של $movie['moods']['results'] עם ה-avg הגבוה ביותר. ראו מדדי מצב רוח.
  • קראו על מגבלות קצב, או השוו עם המדריך ב-Go.