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

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

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

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

קנו תקופה בתשלום מראש בעמוד [תמחור](https://amodb.dev/he/pricing/). אין מסלול חינמי, וכל מסלול מחזיר את מערך הנתונים המלא ב-20, 40 או 120 בקשות לדקה. צרו מפתח בלוח הבקרה של החשבון. הוא 32 תווים הקסדצימליים באותיות קטנות. ייצאו אותו לפני שמפעילים את PHP:

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

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

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

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

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

הפקודה הראשונה אמורה לדווח על 8.2 ומעלה. השנייה חייבת להדפיס `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`, ולכן שחקנים וציונים מגיעים באותה בקשה. פרטי השדות בעמוד [סרטים](https://amodb.dev/he/movies/), וגדלי הפוסטרים בעמוד [תמונות](https://amodb.dev/he/images-api/).

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

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

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

הכותרת מגיעה דרך `CURLOPT_HEADERFUNCTION`, פונקציית קריאה חוזרת ש-cURL מריץ לכל שורת כותרת בתגובה. את רשימת הקודים המלאה ראו בעמוד [שגיאות](https://amodb.dev/he/errors/).

## שלב 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
<?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>
```

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

## הצעדים הבאים

- העבירו את המטמון מתיקיית הזמניים לתיקייה שבשליטתכם, ומחקו קבצים ישנים מיום באמצעות משימת cron.
- הוסיפו `language=ru-RU` או אחת מ-[14 השפות](https://amodb.dev/he/languages/) האחרות ושמרו כל שפה במטמון בנפרד, מה שגיבוב הכתובת כבר עושה.
- הוסיפו `moods` ל-`append_to_response` והדפיסו את הפריט של `$movie['moods']['results']` עם ה-`avg` הגבוה ביותר. ראו [מדדי מצב רוח](https://amodb.dev/he/moods/).
- קראו על [מגבלות קצב](https://amodb.dev/he/rate-limits/), או השוו עם [המדריך ב-Go](https://amodb.dev/he/go-movie-api/).

## שאלות ותשובות

### אילו תוספי PHP הקוד צריך?

רק cURL. תמיכה ב-JSON מובנית ב-PHP 8. הריצו php -m כדי לבדוק ש-curl מופיע. הקוד רץ על PHP 8.2 ומעלה ואין לו תלויות Composer, ולכן אפשר להשליך את שני הקבצים לכל פרויקט.

### איפה מטמון הקבצים שומר את הנתונים?

בתיקייה בשם amodb-cache בתוך תיקיית הזמניים של המערכת. כל בקשה מקבלת קובץ JSON אחד בשם שנגזר מגיבוב של הכתובת שלה. קובץ משמש שוב כל עוד גילו נמוך מה-TTL, שהוא 6 שעות כברירת מחדל.

### איך מעבירים את המפתח ב-PHP-FPM?

הוסיפו env[AMODB_KEY] להגדרות ה-pool, כי תהליכי העבודה לא יורשים את ה-shell שלכם. עם השרת המובנה מספיק המשתנה שייצאתם לפני הרצת php -S. לעולם אל תכתבו את המפתח לקובץ שנמצא תחת שורש האתר.

### מה העמוד צריך לעשות כש-amodb מחזיר שגיאה?

להציג הודעה קצרה ולשלוח 502 כדי שהניטור שלכם ישים לב. רשמו את status_message ביומן בשרת ולעולם אל תדפיסו את המפתח. 401 אומר שגם כל הקריאות הבאות ייכשלו, ולכן תקנו את המפתח לפני ניסיון חוזר.

### האם בטוח להמתין אחרי 429 בתוך בקשת אינטרנט?

שמרו על המתנה קצרה. בקשה שישנה 30 שניות תופסת תהליך עבודה, ולכן הגבילו את ההמתנה בעמודים לערך נמוך, או הגישו עותק ישן מהמטמון. כשמטמון הקבצים במקום, מזהים חוזרים מגיעים רק לעיתים נדירות ל-429.

## עמודים קשורים

- [API פרטי סרט](https://amodb.dev/he/movies/): API פרטי סרט עם אותם נתיבים ו-JSON כמו TMDB v3. קבלו סרט לפי מזהה, הוסיפו קרדיטים, דירוגים, מצבי רוח ופסקולים עם append_to_response, ב-14 שפות.
- [API תמונות ושרת תמונות](https://amodb.dev/he/images-api/): API תמונות סרטים עם נתיבי TMDB v3: הציגו פוסטרים, תמונות רקע, לוגואים ותמונות סצנה לכל כותר, וטענו אותם מ-img.amodb.dev ב-10 גדלים בלי מפתח.
- [אימות](https://amodb.dev/he/authentication/): איך שולחים את מפתח amodb: api_key בשאילתה, כותרת Authorization Bearer או apikey בקריאות OMDb. פורמט המפתח, שמירה, החלפה ושגיאות 401 שכדאי להכיר.
- [שגיאות](https://amodb.dev/he/errors/): כל שגיאה של amodb עם סטטוס HTTP, status_code והודעה של TMDB, וגם מבנה השגיאה של OMDb. מה כל אחת מהן אומרת ומה הלקוח שלכם צריך לעשות כשהיא מופיעה.
- [מדריך API לסרטים ב-Python](https://amodb.dev/he/python-movie-api/): בנו כלי שורת פקודה ב-Python שמחפש כותר סרט ומדפיס שנה, שחקנים, דירוג IMDb ופוסטר, עם ניסיון חוזר ב-429 ומטמון קבצים. משתמש ב-requests, Python 3.10 ומעלה.
- [מדריך API לסרטים ב-JavaScript](https://amodb.dev/he/javascript-movie-api/): כתבו סקריפט Node.js שטוען פרטים, קרדיטים ודירוגים לרשימת מזהי סרטים בבקשה אחת לכל סרט עם append_to_response, עם מטמון TTL, ניסיון חוזר ב-429 ובלי תלויות.

עודכן: 2026-10-18. Source: https://amodb.dev/he/php-movie-api/
