Sitio personal mínimo con PHP: router y caché sin dependencias

Esta guía describe un enfoque minimalista para un sitio personal usando PHP puro: router simple, caché con sqlite y mínimos componentes externos para reducir mantenimiento.

Incluye ejemplos de código para el router, la regla de Apache (.htaccess), el script de cacheo y la tabla sqlite para reproducir el patrón.

Introducción

Para un sitio personal mayoritariamente estático, minimizar dependencias reduce complejidad operativa: menos builds, menos procesos a gestionar y actualizaciones centralizadas por el gestor de paquetes del sistema.

Prerrequisitos

Basado en la descripción original, los requisitos esenciales son:

  • Servidor HTTP (Apache) con soporte para rewrite y uso de index.php como front controller.
  • PHP instalado en el sistema y extensión pdo_sqlite para acceso a sqlite.
  • Acceso para programar tareas (cron) si se usa un script que actualice la caché.
  • Permisos de filesystem para que PHP lea/usen el directorio pages/ y escriba la base sqlite.

Desarrollo

La idea central es un front controller (index.php) que resuelve rutas estáticas buscando ficheros en pages/<path>/index.php o pages/<path>.php y que carga meta datos desde un array PHP.

Procedimiento

Flujo básico del router:

  1. Normalizar REQUEST_URI y eliminar query string.
  2. Componer rutas candidatas: pages/<path>/index.php y pages/<path>.php.
  3. Incluir el primer fichero existente dentro del layout principal o enviar 404 si no existe.

Ejemplo de router sencillo (54 líneas en la implementación original):

<?php
// Simple front controller: index.php
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = trim($uri, '/');
if ($path === '') {
    $path = 'index';
}

$candidates = [
    __DIR__ . '/pages/' . $path . '/index.php',
    __DIR__ . '/pages/' . $path . '.php',
];

$found = null;
foreach ($candidates as $file) {
    if (file_exists($file)) {
        $found = $file;
        break;
    }
}

// Metadata map: ruta => título (ejemplo mínimo)
$meta = [
    'index' => 'Inicio',
    'music' => 'Música',
];

if ($found) {
    $title = $meta[$path] ?? 'Página';
    // variables disponibles para el layout
    $contentFile = $found;
    include __DIR__ . '/templates/layout.php';
    exit;
}

http_response_code(404);
include __DIR__ . '/pages/404.php';
Lenguaje del código: PHP (php)

El layout (templates/layout.php) simplemente incluye el fichero de contenido dentro de la estructura HTML del sitio y usa $title para el <title>.

Ejemplos

A continuación se muestran fragmentos prácticos: la regla de Apache para ocultar index.php, la tabla sqlite, y un script cron que actualiza la caché.

Regla mínima en .htaccess para enviar todas las solicitudes a index.php salvo ficheros existentes:

RewriteEngine On
# Si el fichero o directorio existe, servirlo directamente
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]
# En caso contrario, reenviar a index.php
RewriteRule ^ index.php [L,QSA]
Lenguaje del código: texto plano (plaintext)

Ejemplo de esquema sqlite para almacenar elementos cacheados (tabla simple):

CREATE TABLE IF NOT EXISTS blog_cache (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  source TEXT NOT NULL,
  title TEXT NOT NULL,
  excerpt TEXT,
  featured_image TEXT,
  url TEXT NOT NULL,
  fetched_at INTEGER NOT NULL
);
Lenguaje del código: SQL (Structured Query Language) (sql)

Script de ejemplo (cron) que ejecuta un script PHP para actualizar la caché desde fuentes externas:

# Entrar en /var/www/site y ejecutar el actor que actualiza sqlite
cd /var/www/site || exit 1
/usr/bin/php scripts/update_blog_cache.php
Lenguaje del código: Bash (bash)

Fragmento PHP que actualiza sqlite usando PDO: (ejemplo mínimo, adaptar parsing de cada fuente)

<?php
$db = new PDO('sqlite:' . __DIR__ . '/../data/blog_cache.sqlite3');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Ejemplo: array de fuentes (URLs) a procesar
$sources = [
    'https://example.com/feed',
];

$stmt = $db->prepare('INSERT INTO blog_cache (source,title,excerpt,featured_image,url,fetched_at) VALUES (:source,:title,:excerpt,:image,:url,:ts)');

foreach ($sources as $src) {
    // Simplificado: obtener HTML/JSON y extraer títulos; aquí se usa file_get_contents como ejemplo
    $body = @file_get_contents($src);
    if ($body === false) {
        continue;
    }

    // Extraer ítems: esto depende del formato de la fuente. Aquí se asume extracción manual.
    // Insertar un ejemplo ficticio de fila (reemplazar por parsing real)
    $stmt->execute([
        ':source' => $src,
        ':title' => 'Título ejemplo',
        ':excerpt' => 'Extracto ejemplo',
        ':image' => '',
        ':url' => $src,
        ':ts' => time(),
    ]);
}
Lenguaje del código: PHP (php)

En la página que muestra la lista de blogs, lee la tabla blog_cache para renderizar los últimos registros en lugar de pedir cada feed en tiempo real.

Checklist

  1. Instalar PHP y habilitar pdo_sqlite desde el gestor de paquetes del sistema.
  2. Configurar Apache y añadir la regla .htaccess para el front controller.
  3. Crear estructura de carpetas: pages/, templates/, data/ y scripts/.
  4. Implementar el script cron para actualizar la caché sqlite y verificar permisos de escritura.
  5. Probar rutas estáticas y 404, revisar logs de Apache y PHP para errores.

Conclusión

Un sitio personal ligero implementado con PHP puro permite mantener control total sobre la superficie de mantenimiento al costo de escribir piezas pequeñas como el router y scripts de cacheo.

Esta aproximación favorece simplicidad operativa: menos dependencias, actualizaciones gestionadas por el sistema y procesos reducidos. Para sitios personales mayoritariamente estáticos, la diferencia frente a soluciones estáticas o frameworks pesados suele ser negligente en rendimiento y sustancial en simplicidad.

Si quieres, puedo adaptar los ejemplos para una estructura concreta de páginas, o convertir el router en una versión con logging y manejo básico de errores.

Comments

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *