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

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

.php PHP Оновлено Відкрити в Markdown

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

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

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

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

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 потрібні чотири речі: 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, тож акторський склад і оцінки надходять в одному запиті. Подробиці полів на сторінці фільми, а розміри постерів на сторінці API зображень.

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

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

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

Заголовок надходить через CURLOPT_HEADERFUNCTION, функцію зворотного виклику, яку cURL запускає для кожного рядка заголовка відповіді. Повний список кодів дивіться на сторінці помилки.

Крок 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 -S localhost:8000 і відкрийте http://localhost:8000/movie.php?id=603. URL постера вказує на хост зображень, якому ключ не потрібен.

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 мов і кешуйте кожну мову окремо, що хеш URL уже забезпечує.
  • Додайте moods до append_to_response і надрукуйте запис з $movie['moods']['results'] з найбільшим avg. Дивіться настрої.
  • Прочитайте про ліміти запитів або порівняйте з посібником Go.