Tutorial de API de películas en PHP
Crea una función PHP reutilizable y una página de película mínima. La función llama a amodb con cURL, lee Retry-After ante un 429 y guarda cada respuesta en un archivo durante 6 horas. La página muestra póster, reparto y nota de IMDb. Requiere PHP 8.2 o superior con la extensión curl y lleva unos 20 minutos.
Vas a crear dos archivos pequeños. amodb.php contiene una función, amodb_get(), que llama a la API con cURL y guarda las respuestas en archivos. movie.php es una página que muestra una película por id con póster, reparto y nota de IMDb. Necesitas PHP 8.2 o superior con la extensión curl y unos 20 minutos.
Paso 1: obtén una clave de API
Compra un periodo prepago en precios. No hay nivel gratuito, y todos los planes devuelven el conjunto completo de datos con 20, 40 o 120 solicitudes por minuto. Crea una clave en el panel de tu cuenta. Tiene 32 caracteres hexadecimales en minúscula. Expórtala antes de iniciar PHP:
El código la lee con getenv('AMODB_KEY'). Con PHP-FPM, los workers no ven las variables de tu terminal, así que define env[AMODB_KEY] en la configuración del pool.
export AMODB_KEY="YOUR_API_KEY"Paso 2: prepara el proyecto
Crea una carpeta y comprueba que cURL está disponible:
El primer comando debe informar 8.2 o superior. El segundo debe imprimir curl.
mkdir movie-page && cd movie-page
php -v
php -m | grep -i curlPaso 3: envía la primera solicitud
Una llamada básica de cURL necesita cuatro cosas: la URL, un tiempo de espera, el encabezado Authorization y una comprobación de estado. En PHP 8 el handle se libera solo, así que no hay 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;Paso 4: envuélvelo en una función
Llamar a cURL desde cada página repite la misma configuración. amodb_ construye la URL con http_build_query, envía la llamada y devuelve un array decodificado. La página de película pasa append_, así que el reparto y las puntuaciones llegan en la misma solicitud. Los detalles de los campos están en películas, y los tamaños de póster en imágenes.
Paso 5: gestiona errores y límites de solicitudes
La función lanza AmodbException ante cualquier fallo, y la página lo captura en un solo lugar.
| Estado | Qué hace la función |
|---|---|
| 401 | Lanza la excepción de inmediato. Reintentar no arregla una clave incorrecta o suspendida. |
| 404 | Lanza la excepción con el status_message, para que la página pueda decir que el id no existe. |
| 429 | Lee el encabezado Retry-After, espera esa cantidad de segundos (de 1 a 30) y reintenta hasta dos veces. |
| 5xx | Lanza la excepción con el código de estado y el mensaje. |
| Error de cURL | Lanza Network error con el texto de curl_error(). |
El encabezado llega mediante CURLOPT_HEADERFUNCTION, una función de retorno que cURL ejecuta por cada línea de encabezado de la respuesta. Consulta errores para ver la lista completa de códigos.
Paso 6: guarda las respuestas en archivos
El caché perimetral mantiene los detalles durante 6 horas. Un archivo local ahorra la solicitud en sí, y un acierto de caché no gasta nada de tu límite. Las antigüedades se suman, así que un archivo de 6 horas puede contener datos que ya tenían varias horas en el perímetro. Reduce el TTL si eso importa. La función calcula el hash sha1 de la URL final, busca un archivo con ese nombre y lo devuelve cuando su fecha de modificación es más reciente que el TTL. Si no, obtiene la respuesta, escribe el cuerpo con LOCK_EX y devuelve los datos.
Paso 7: el programa completo
Guarda la función como 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);
}Después guarda la página como movie.php. Cada valor pasa por htmlspecialchars antes de llegar al HTML.
Inicia el servidor integrado con php -S localhost:8000 y abre http:/. La URL del póster apunta al host de imágenes, que no necesita clave.
<?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>Próximos pasos
- Mueve el caché de la carpeta temporal a un directorio que controles y borra los archivos de más de un día con una tarea de cron.
- Añade
language=ru-RUu otro de los 14 idiomas y guarda cada idioma por separado en caché, algo que el hash de la URL ya hace. - Añade
moodsaappend_to_responsee imprime la entrada de$movie['moods']['results']con elavgmás alto. Consulta moods (ambiente emocional). - Lee sobre los límites de solicitudes o compáralo con el tutorial de Go.