PHP 电影 API 教程
构建一个可复用的 PHP 函数和一个小型电影页面。该函数用 cURL 调用 amodb,429 时读取 Retry-After,并把响应缓存 6 小时。页面显示海报、演员和 IMDb 评分。需要 PHP 8.2 或更新版本及 curl 扩展,约 20 分钟。
你将构建两个小文件。amodb.php 包含一个函数 amodb_get(),用 cURL 调用 API 并把响应缓存到文件。movie.php 是一个页面,按 ID 显示电影的海报、演员和 IMDb 评分。你需要带有 curl 扩展的 PHP 8.2 或更新版本,以及大约 20 分钟。
步骤 1:获取 API 密钥
在定价页购买一个预付周期。没有免费额度,每个套餐都以每分钟 20、40 或 120 个请求返回完整数据集。在账户后台创建密钥,它是 32 个小写十六进制字符。启动 PHP 之前先导出它:
代码用 getenv('AMODB_KEY') 读取它。在 PHP-FPM 下,工作进程看不到你的 shell,所以请改为在 pool 配置中设置 env[AMODB_KEY]。
export AMODB_KEY="YOUR_API_KEY"mkdir movie-page && cd movie-page
php -v
php -m | grep -i curl步骤 3:发送第一个请求
最简单的 cURL 调用需要四样东西:URL、超时、Authorization 请求头和状态检查。在 PHP 8 中句柄会自动释放,所以不需要 curl_close()。
<?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_ 用 http_build_query 构建 URL,发送调用并返回解码后的数组。电影页面传入 append_,所以演员和评分在同一个请求中返回。字段详情见电影,海报尺寸见图片。
步骤 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。函数用 sha1 对最终 URL 取哈希,查找同名文件,并在其修改时间比 TTL 更新时返回它。否则就发起请求,用 LOCK_EX 写入响应体并返回数据。
步骤 7:完整程序
把函数保存为 amodb.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。每个值在进入 HTML 之前都会经过 htmlspecialchars。
用 php -S localhost:8000 启动内置服务器,打开 http:/。海报 URL 指向图片主机,无需密钥。
<?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>