Peticiones HTTP en PHP: para hablar con una API desde PHP, la extensión cURL viene en casi todas las instalaciones y lo permite todo. Lo que hay que saber es configurarla bien y distinguir un error de red de una respuesta de error.
También te puede interesar
Los ejemplos de esta página hablan con un pequeño servidor de pruebas que arrancamos en nuestra máquina, en http://localhost:8000, para que las respuestas sean siempre las mismas. Con cualquier API real funciona igual cambiando la dirección. No se pueden probar en el editor del navegador, que no tiene acceso a la red.
GET y JSON
<?php
declare(strict_types=1);
$ch = curl_init('http://localhost:8000/productos');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);
$cuerpo = curl_exec($ch);
$codigo = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($cuerpo === false) {
throw new RuntimeException('Fallo de red: ' . curl_error($ch));
}
echo "Código $codigo\n";
foreach (json_decode($cuerpo, true, flags: JSON_THROW_ON_ERROR) as $p) {
echo "- {$p['nombre']}: {$p['precio']}\n";
}salidaCódigo 200 - Café: 12.5 - Taza: 8
curl_init crea la petición, curl_setopt_array la configura y curl_exec la envía. Sin CURLOPT_RETURNTRANSFER, la respuesta se imprimiría directamente en vez de devolverse. curl_exec devuelve false solo si falló la red (no hay conexión, el nombre no existe, se agotó el tiempo); el código HTTP se pregunta aparte con curl_getinfo. Desde PHP 8 no hace falta curl_close: la petición se libera sola.
POST con JSON y cabeceras
<?php
declare(strict_types=1);
$datos = ['nombre' => 'Ana', 'edad' => 31];
$ch = curl_init('http://localhost:8000/eco');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($datos, JSON_THROW_ON_ERROR),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . (getenv('API_TOKEN') ?: 'token-de-prueba'),
],
]);
$respuesta = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), "\n";
echo $respuesta, "\n";salida201
{"recibido":{"nombre":"Ana","edad":31},"tipo":"application\/json","token":"Bearer token-de-prueba"}Para enviar JSON, el cuerpo se codifica con json_encode y se avisa con la cabecera Content-Type. El servidor de prueba devuelve lo que recibió, con un 201 («creado»). La clave de la API se lee de una variable de entorno, nunca escrita en el código: lo explicamos en configuración con variables de entorno.
Un 404 no es un error de cURL
<?php
declare(strict_types=1);
function pedir(string $url): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
$cuerpo = curl_exec($ch);
if ($cuerpo === false) {
throw new RuntimeException(curl_error($ch));
}
$codigo = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($codigo >= 400) {
throw new RuntimeException("El servidor respondió $codigo: $cuerpo");
}
return json_decode($cuerpo, true, flags: JSON_THROW_ON_ERROR);
}
try {
pedir('http://localhost:8000/no-existe');
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}salidaEl servidor respondió 404: {"error": "No existe"}El tiempo de espera
<?php
declare(strict_types=1);
$ch = curl_init('http://localhost:8000/lento');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 1]);
$inicio = microtime(true);
$cuerpo = curl_exec($ch);
printf("%s tras %.1f s\n", $cuerpo === false ? 'Error: ' . curl_error($ch) : 'Respuesta', microtime(true) - $inicio);salidaError: Operation timed out after 1002 milliseconds with 0 bytes received tras 1.0 s
CURLOPT_TIMEOUT, cURL espera indefinidamente a un servidor que no contesta, y tu página se queda colgada con él. Pon siempre un tiempo máximo (CURLOPT_TIMEOUT) y otro para conectar (CURLOPT_CONNECTTIMEOUT). Y si la URL la elige un usuario, valídala antes: lo explicamos en SSRF, en los fallos de seguridad típicos.¿Y file_get_contents?
file_get_contents('https://…') también descarga una URL y vale para un caso rápido, pero cuesta más controlar el tiempo de espera, las cabeceras y los errores. Para una aplicación, cURL o una biblioteca como Guzzle (composer require guzzlehttp/guzzle), que lo envuelve en una interfaz más cómoda. Si la usas, mantenla al día: en la lección de Composer vimos cómo composer audit encontró 14 avisos de seguridad en una versión antigua.
Resumen
| Para | Usa |
|---|---|
| Devolver la respuesta | CURLOPT_RETURNTRANSFER => true |
| Enviar JSON | CURLOPT_POSTFIELDS => json_encode(...) y Content-Type |
| Código de estado | curl_getinfo($ch, CURLINFO_RESPONSE_CODE) |
| Error de red | curl_exec devuelve false; curl_error($ch) |
| Limitar la espera | CURLOPT_TIMEOUT y CURLOPT_CONNECTTIMEOUT |
Todo el código de esta página se ejecutó con PHP 8.5.12 en un contenedor limpio, contra un servidor de pruebas propio arrancado con php -S dentro del mismo contenedor y sin acceso a internet; las salidas están copiadas de esa ejecución.