# Посібник з PHP API фільмів

> Створіть одну повторно використовувану функцію PHP і крихітну сторінку фільму. Функція викликає amodb через cURL, читає Retry-After при 429 і кешує кожну відповідь у файлі на 6 годин. Сторінка показує постер, акторів і оцінку IMDb. Потрібен PHP 8.2 або новіший із розширенням curl, це займає близько 20 хвилин.

Ви створите два невеликі файли. `amodb.php` містить одну функцію `amodb_get()`, що викликає API через cURL і кешує відповіді у файлах. `movie.php` це сторінка, яка показує фільм за id із постером, акторами та оцінкою IMDb. Вам потрібен PHP 8.2 або новіший із розширенням `curl` і близько 20 хвилин.

## Крок 1: отримайте ключ API

Придбайте передплачений період на сторінці [ціни](https://amodb.dev/uk/pricing/). Безкоштовного тарифу немає, а кожен тариф повертає повний набір даних зі швидкістю 20, 40 або 120 запитів на хвилину. Створіть ключ у панелі облікового запису. Він складається з 32 шістнадцяткових символів малими літерами. Експортуйте його перед запуском PHP:

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

Код читає його через `getenv('AMODB_KEY')`. Під PHP-FPM робочі процеси не бачать вашу оболонку, тож натомість задайте `env[AMODB_KEY]` у конфігурації пулу.

## Крок 2: підготуйте проєкт

Створіть теку й перевірте, що cURL доступний:

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

Перша команда має показати 8.2 або вище. Друга має вивести `curl`.

## Крок 3: надішліть перший запит

Для простого виклику cURL потрібні чотири речі: URL, тайм-аут, заголовок `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)` будує URL через `http_build_query`, надсилає виклик і повертає декодований масив. Сторінка фільму передає `append_to_response=credits,ratings`, тож акторський склад і оцінки надходять в одному запиті. Подробиці полів на сторінці [фільми](https://amodb.dev/uk/movies/), а розміри постерів на сторінці [API зображень](https://amodb.dev/uk/images-api/).

## Крок 5: обробка помилок і лімітів запитів

Функція викидає `AmodbException` для кожного збою, а сторінка перехоплює його в одному місці.

| Статус       | Що робить функція                                                                                |
| ------------ | ------------------------------------------------------------------------------------------------ |
| 401          | Викидає виняток одразу. Хибний або призупинений ключ не виправити повтором.                      |
| 404          | Викидає виняток із `status_message`, щоб сторінка могла сказати, що id невідомий.                |
| 429          | Читає заголовок `Retry-After`, спить стільки секунд (від 1 до 30), потім повторює до двох разів. |
| 5xx          | Викидає виняток із кодом статусу й повідомленням.                                                |
| Помилка cURL | Викидає `Network error` з текстом із `curl_error()`.                                             |

Заголовок надходить через `CURLOPT_HEADERFUNCTION`, функцію зворотного виклику, яку cURL запускає для кожного рядка заголовка відповіді. Повний список кодів дивіться на сторінці [помилки](https://amodb.dev/uk/errors/).

## Крок 6: кешуйте відповіді у файлах

Кеш на периферії зберігає деталі 6 годин. Локальний файл економить сам запит, а влучання в кеш нічого не витрачає з вашого ліміту. Терміни додаються, тож файл віком 6 годин може містити дані, які на периферії вже мали вік у кілька годин. Зменште TTL, якщо це важливо. Функція хешує підсумковий URL через `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`. URL постера вказує на хост зображень, якому ключ не потрібен.

## Наступні кроки

- Перенесіть кеш із тимчасової теки в каталог, який ви контролюєте, і видаляйте файли старші за добу завданням cron.
- Додайте `language=ru-RU` або іншу з [14 мов](https://amodb.dev/uk/languages/) і кешуйте кожну мову окремо, що хеш URL уже забезпечує.
- Додайте `moods` до `append_to_response` і надрукуйте запис з `$movie['moods']['results']` з найбільшим `avg`. Дивіться [настрої](https://amodb.dev/uk/moods/).
- Прочитайте про [ліміти запитів](https://amodb.dev/uk/rate-limits/) або порівняйте з [посібником Go](https://amodb.dev/uk/go-movie-api/).

## Запитання й відповіді

### Які розширення PHP потрібні коду?

Лише cURL. Підтримка JSON вбудована в PHP 8. Виконайте php -m, щоб перевірити, що curl є в списку. Код працює на PHP 8.2 і новіших та не має залежностей Composer, тож два файли можна додати в будь-який проєкт.

### Де файловий кеш зберігає дані?

У теці amodb-cache всередині системної тимчасової теки. Кожен запит отримує один файл JSON з назвою на основі хешу його URL. Файл використовується, поки його вік менший за TTL, який за замовчуванням становить 6 годин.

### Як передати ключ під PHP-FPM?

Додайте env[AMODB_KEY] до конфігурації пулу, бо робочі процеси не успадковують вашу оболонку. З вбудованим сервером достатньо змінної, яку ви експортували перед php -S. Ніколи не записуйте ключ у файл під веб-коренем.

### Що має робити сторінка, коли amodb повертає помилку?

Показати коротке повідомлення й надіслати 502, щоб ваш моніторинг це помітив. Записуйте status_message у журнал на сервері й ніколи не друкуйте ключ. Відповідь 401 означає, що всі наступні виклики теж провалюються, тож виправте ключ перед повтором.

### Чи безпечно викликати sleep при 429 усередині веб-запиту?

Тримайте затримку короткою. Запит, що спить 30 секунд, займає робочий процес, тож на сторінках обмежуйте очікування низько або віддавайте застарілу кешовану копію. Із файловим кешем повторні id рідко доходять до 429.

## Пов'язані сторінки

- [API деталей фільму](https://amodb.dev/uk/movies/): API деталей фільму з тими самими шляхами та JSON, що в TMDB v3. Отримайте фільм за id, додайте акторів, рейтинги й настрої через append_to_response, 14 мовами.
- [API зображень і хост зображень](https://amodb.dev/uk/images-api/): API зображень фільмів зі шляхами TMDB v3: списки постерів, фонів, логотипів і кадрів проєкту, а потім завантаження з img.amodb.dev у 10 розмірах без ключа.
- [Автентифікація](https://amodb.dev/uk/authentication/): Як надсилати ключ amodb: api_key у запиті, заголовок Authorization Bearer або apikey для викликів OMDb. Формат ключа, зберігання, заміна та помилки 401.
- [Помилки](https://amodb.dev/uk/errors/): Усі помилки amodb зі статусом HTTP, status_code і повідомленням TMDB, а також форма помилки OMDb. Що означає кожна й що має робити ваш клієнт далі.
- [Посібник з Python API фільмів](https://amodb.dev/uk/python-movie-api/): Створіть консольний інструмент Python, що шукає фільм за назвою й друкує рік, акторів, рейтинг IMDb і постер, з повтором при 429 і файловим кешем. На requests.
- [Посібник з JavaScript API фільмів](https://amodb.dev/uk/javascript-movie-api/): Напишіть скрипт Node.js, що завантажує деталі, акторів і рейтинги для списку id фільмів одним запитом на кожен, з TTL-кешем і повтором при 429. Без залежностей.

Оновлено: 2026-10-18. Source: https://amodb.dev/uk/php-movie-api/
