5 mejores prácticas para construir APIs RESTful robustas en PHP

php 5 mejores prácticas para construir APIs RESTful robustas en PHP

5 mejores prácticas para construir APIs RESTful robustas en PHP

Este artículo va directo al grano: prácticas comprobadas para diseñar, implementar y mantener APIs RESTful en PHP que sean seguras, mantenibles y fáciles de escalar. Incluye estructura de proyecto, ejemplos de código, middleware esencial y por qué cada práctica importa.

Estructura mínima recomendada

Mantén claro el flujo entre entrada, lógica y persistencia:

my-api/
├─ public/
│  └─ index.php        # punto de entrada (servidor web apunta aquí)
├─ src/
│  ├─ Controller/
│  │  └─ UserController.php
│  ├─ Repository/
│  │  └─ UserRepository.php
│  ├─ Middleware/
│  │  └─ AuthMiddleware.php
│  └─ bootstrap.php
├─ config/
│  └─ settings.php
├─ tests/
└─ composer.json

1) Entradas y respuestas: JSON, validación y errores consistentes

Forzar JSON, validar antes de tocar la lógica y devolver errores deterministas con códigos HTTP y un esquema fijo.

<?php
// public/index.php
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../src/bootstrap.php';

// Forzar JSON
if (strpos($_SERVER['CONTENT_TYPE'] ?? '', 'application/json') === false && 
    in_array($_SERVER['REQUEST_METHOD'], ['POST','PUT','PATCH'])) {
    http_response_code(415);
    echo json_encode(['error' => 'Unsupported Media Type, use application/json']);
    exit;
}

// Simple router (por claridad)
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];

if ($path === '/users' && $method === 'POST') {
    $body = json_decode(file_get_contents('php://input'), true);
    if (!is_array($body)) {
        http_response_code(400);
        echo json_encode(['error' => 'Invalid JSON']);
        exit;
    }

    // Validación mínima
    if (empty($body['email']) || !filter_var($body['email'], FILTER_VALIDATE_EMAIL)) {
        http_response_code(422);
        echo json_encode(['error' => 'Email inválido']);
        exit;
    }

    // Lógica: delegar a repository
    $repo = new UserRepository($pdo);
    $id = $repo->create($body['email'], $body['name'] ?? null);

    http_response_code(201);
    echo json_encode(['id' => $id]);
    exit;
}

http_response_code(404);
echo json_encode(['error' => 'Not found']);

Por qué: validar tempranamente evita efectos secundarios y asegura respuestas predecibles. Usa códigos HTTP correctos: 400, 401, 403, 404, 422, 500.

2) Acceso a datos: usa PDO con sentencias preparadas y un repository

<?php
// src/Repository/UserRepository.php
class UserRepository
{
    private $pdo;
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function create(string $email, ?string $name): int
    {
        $stmt = $this->pdo->prepare('INSERT INTO users (email, name) VALUES (:email, :name)');
        $stmt->execute(['email' => $email, 'name' => $name]);
        return (int) $this->pdo->lastInsertId();
    }

    public function findById(int $id): ?array
    {
        $stmt = $this->pdo->prepare('SELECT id, email, name FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }
}

Por qué: PDO con prepared statements previene inyección SQL. Separar en repositories facilita testing y swapping de persistencia.

3) Autenticación y autorización: JWT + middleware

Implementa autenticación como middleware que valida el token antes de permitir acceso a controladores protegidos.

<?php
// src/Middleware/AuthMiddleware.php
use Firebase\JWT\JWT;

class AuthMiddleware
{
    private $secret;
    public function __construct(string $secret) { $this->secret = $secret; }
    public function handle()
    {
        $h = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
        if (!preg_match('/^Bearer\s+(\S+)$/', $h, $m)) {
            http_response_code(401);
            echo json_encode(['error' => 'Missing token']);
            exit;
        }
        $token = $m[1];
        try {
            $payload = JWT::decode($token, $this->secret, ['HS256']);
            // Attach user to request context (simple example)
            $GLOBALS['user'] = $payload;
        } catch (Exception $e) {
            http_response_code(401);
            echo json_encode(['error' => 'Invalid token']);
            exit;
        }
    }
}

Por qué: JWT es simple y sin estado para APIs. Ten cuidado: almacena solo IDs y expiración, y rota secretos periódicamente.

4) Rate limiting, caching y paginación

Protege tu API de abusos y ofrece respuestas eficientes:

  • Rate limiting: usar Redis para contar peticiones por IP/usuario.
  • Caching: respuestas idempotentes (GET) con ETag o Cache-Control.
  • Paginación: usa limit/offset o keyset pagination; incluye enlaces next/prev.
<?php
// Simple rate limit pseudo-código usando Redis
$ip = $_SERVER['REMOTE_ADDR'];
$key = "rate:{$ip}";
$remaining = $redis->incr($key);
if ($remaining == 1) $redis->expire($key, 60); // window 60s
$limit = 60;
if ($remaining > $limit) {
    http_response_code(429);
    header('Retry-After: 60');
    echo json_encode(['error' => 'Too many requests']);
    exit;
}

5) Documentación, testing y observabilidad

Documenta con OpenAPI (swagger) y cubre lo crítico con tests automatizados; agrega logs estructurados y métricas (latencia, error rate).

// Ejemplo mínimo de test con PHPUnit (tests/UserTest.php)
public function testCreateUser()
{
    $response = $this->httpPost('/users', ['email' => 'a@b.com', 'name' => 'A']);
    $this->assertEquals(201, $response->getStatusCode());
    $body = json_decode($response->getBody(), true);
    $this->assertArrayHasKey('id', $body);
}

Por qué: documentación y tests reducen fricción para integradores y evitan regresiones.

Buenas prácticas rápidas (lista)

  • Versiona tu API (/v1/users) desde el principio.
  • No reveles detalles internos en errores de 500.
  • Usa HTTPS obligatorio y HSTS.
  • Límita campos serializados (avoid overfetching) y permite includes.
  • Gestiona CORS de forma explícita, no con comodines en producción.

Por qué enfocarse en estas prácticas

Las APIs fallan por problemas repetibles: validación insuficiente, falta de límites, secretos mal gestionados y malas practicas de persistencia. Adoptar patrones simples —middleware para cross-cutting concerns, repositorios para datos, respuestas consistentes— reduce errores y acelera el desarrollo.

Siguiente paso avanzado

Aplica un enfoque schema-first: define tu API en OpenAPI, genera contratos y pruebas automáticas; añade un gateway (por ejemplo Kong o Amazon API Gateway) para centralizar rate limiting, logging y autenticación. Atención: audita secretos y rotaciones si usas JWT en producción.

Advertencia: incluso con JWT, protege endpoints críticos con scopes y checks en el backend; nunca confíes solo en expiraciones largas.

Comentarios
¿Quieres comentar?

Inicia sesión con Telegram para participar en la conversación


Comentarios (0)

Aún no hay comentarios. ¡Sé el primero en comentar!

Iniciar Sesión