ArtiFrame Documentación
El framework de proyectos PHP nativos escalables administrados con cero dependencias externas, reglas estrictas y una poderosa CLI. Sin dependencias, libre del caos de paquetes de composer, un ecosistema ligero y rápido que le permite enfocarse en su lógica de negocio.
Introducción y Filosofía
ArtiFrame se basa en el principio de "Convención sobre Configuración". El poder de un framework no proviene de la riqueza de las herramientas que ofrece, sino de la coherencia del orden que establece.
Cero Sobrecarga (Zero Overhead)
Cero dependencias de paquetes de Composer, núcleo del framework o bibliotecas de terceros. Cada línea de código es suya; libre de inflamiento, libre de capas de abstracción innecesarias.
La Seguridad es lo Primero
La protección XSS, validación CSRF, sanitización contra inyección SQL y el control de métodos HTTP están integrados por defecto. La seguridad no es una opción, es un estándar.
Conjunto Estricto de Reglas
Un desarrollador junior recién incorporado al proyecto comprende la arquitectura data-js y la estructura de directorios en minutos. La armonía del equipo está garantizada a nivel del framework.
CLI como Prioridad
No se crean archivos de vista, API o clase manualmente. La CLI genera desde archivos stub, configura los enlaces de assets y mantiene el proyecto coherente.
Instalación
La CLI de ArtiFrame se instala como una herramienta PHP global. Se instala una vez, se usa en todos los proyectos.
1. Instale la Herramienta CLI Globalmente
# Instalación global vía NPM
npm install -g @artilingo/artiframe-cli
# Verifique la instalación
artiframe
2. Shell Interactivo
Simplemente escriba artiframe en la terminal y presione Enter. La CLI no se cierra; se abre un shell interactivo que escucha comandos continuamente:
==================================================
ArtiFrame CLI Interactive Shell v1.0.0
==================================================
Type 'help' for commands, or 'exit' to quit.
artiframe>
3. Iniciar un Nuevo Proyecto
artiframe> new mi-proyecto
Este comando crea el directorio mi-proyecto/ y copia todo el esqueleto estructural dentro de él: app/, bin/, config/, public/, src/, .env.example y la primera página index.php.
4. Configuración del Entorno
cp .env.example .env
Abra su archivo .env y rellene la información de la base de datos y la aplicación. Este archivo nunca entra al control de versiones.
/public/. Otros directorios nunca deben estar expuestos al exterior.
Estructura de Directorios
La arquitectura generada cuando se inicia el proyecto; asegura una separación clara de responsabilidades (SoC).
bin/ son el núcleo del framework. No se añade lógica de negocio específica del proyecto aquí. Las clases y servicios que agregue van debajo de src/, los componentes de infraestructura van debajo de app/.
Arquitectura Bootstrapper Crítico
ArtiFrame usa dos bootstrappers completamente independientes. Esta arquitectura previene inherentemente problemas de encabezados HTML y vulnerabilidades de seguridad.
View ViewControl.php
Se usa para páginas HTML (archivos view). Inicia sesión, carga ViewMethod.
// public/perfil.php — HASTA ARRIBA, antes de imprimir HTML
<?php
require_once $_SERVER['DOCUMENT_ROOT']
. '/../app/ViewControl.php';
use Bin\ViewMethod;
?>
<!DOCTYPE html>
...
API ApiControl.php
Se usa para archivos de endpoints API. Establece el encabezado JSON, comprueba el método HTTP, carga SystemMethod.
// public/api/usuario/obtener.php
<?php
// $allowedMethods DEBE definirse antes del require
$allowedMethods = ['GET'];
require_once $_SERVER['DOCUMENT_ROOT']
. '/../app/ApiControl.php';
use Bin\SystemMethod;
Conjunto de Reglas Estándar
Regla 1: Arquitectura data-js Crítico
Los eventos de JavaScript nunca deben escucharse a través de un class o id. Estos son identificadores visuales / de estilo. Todas las interacciones de JS se manejan mediante el atributo data-js. Cuando el CSS elimina una clase, JavaScript nunca se rompe.
<!-- ❌ Anti-Patrón — no soportado -->
<button id="submitBtn" class="btn">Enviar</button>
// JS: document.getElementById('submitBtn').addEventListener(...)
<!-- ✅ Estándar ArtiFrame -->
<button class="btn btn-primary" data-js="login-submit">Enviar</button>
// JS: document.querySelector('[data-js="login-submit"]').addEventListener(...)
Regla 2: Arquitectura de Temas
El modo Oscuro/Claro y los temas se administran a través de los atributos data-theme y data-mode de la etiqueta <html>. No se utilizan clases en el body.
<!-- Etiqueta de apertura HTML de la plantilla view.stub -->
<html lang="es" data-theme="default" data-mode="light">
/* Definición del tema en app.css */
html[data-theme="default"][data-mode="dark"] {
--bg-color: #0b0c0e;
--text-main: #ffffff;
}
html[data-theme="default"][data-mode="light"] {
--bg-color: #ffffff;
--text-main: #111111;
}
Regla 3: Flujo de Datos Seguro
Cada dato que proviene de la base de datos se envuelve con display() antes de imprimirse en el DOM. Cada dato que llega a la API se limpia con sanitizeString() o sanitizeInt() antes de ser procesado.
Regla 4: Manejo de Errores con APP_ENV
El valor de APP_ENV en el archivo .env determina la visibilidad del error. En Producción (Production), no se muestran mensajes de error al usuario.
// config/app-version.php
define('APP_ENV', (int)$_ENV['APP_ENV']); // 1=Debug, 0=Production
if (APP_ENV === 1) {
ini_set('display_errors', 1);
error_reporting(E_ALL);
} else {
ini_set('display_errors', 0);
}
Ecosistema CLI
La CLI de ArtiFrame abre un shell interactivo cuando escribe artiframe en la terminal. Todos los comandos se ejecutan dentro de este shell. Los comandos también se pueden ejecutar de forma puntual.
# Modo interactivo (recomendado)
artiframe
artiframe> make:view admin/usuarios.php
# Modo puntual (una sola vez)
artiframe make:view admin/usuarios.php
new Comando CLI
Crea un nuevo proyecto ArtiFrame. Crea todo el esqueleto de directorios, los archivos bootstrapper y la página index inicial.
artiframe> new nombre-proyecto
Estructura creada:
make:view Comando CLI
Crea un nuevo archivo de página (view) y sus recursos CSS/JS específicos. Los recursos se vinculan automáticamente a la página y se aplica la invalidación de caché (cache-busting) con ?v=APP_VERSION.
artiframe> make:view admin/usuarios.php
Archivos creados:
✔ public/admin/usuarios.php
✔ public/assets/css/admin/usuarios.css
✔ public/assets/js/admin/usuarios.js
El archivo de vista creado viene con ViewControl requerido al principio, los includes head/header/footer añadidos, y los enlaces CSS/JS conectados con cache-busting.
make:api Comando CLI
Crea un archivo de endpoint API eligiendo una de las dos plantillas diferentes. En cada nuevo archivo de API, la variable $allowedMethods y el requerimiento de ApiControl.php ya vienen preparados.
standart — API de Acción Única
Para endpoints que realizan un solo trabajo (iniciar sesión, enviar, borrar). La lógica de negocio se escribe directamente.
artiframe> make:api standart api/auth/entrar.php
<?php
$allowedMethods = ['POST']; // Aceptar solo POST
require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';
use Bin\SystemMethod;
// Lógica de negocio aquí...
jsonResponse(['status' => 'success'], 200);
switch-case — API de Múltiples Acciones
Una estructura que maneja operaciones CRUD para un módulo en un único endpoint. Qué acción se debe realizar se determina por el parámetro action.
artiframe> make:api switch-case api/usuario/administrar.php
<?php
$allowedMethods = ['POST'];
require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';
use Bin\SystemMethod;
$action = sanitizeString($_POST['action'] ?? '');
switch ($action) {
case 'create':
jsonResponse(['status' => 'success', 'message' => 'Creado.'], 200);
break;
case 'update':
jsonResponse(['status' => 'success', 'message' => 'Actualizado.'], 200);
break;
case 'delete':
jsonResponse(['status' => 'success', 'message' => 'Eliminado.'], 200);
break;
default:
jsonResponse(['status' => 'error', 'message' => 'Acción no válida.'], 400);
}
make:class Comando CLI
Crea un nuevo archivo de clase PHP con el namespace y la estructura básica (boilerplate) preparados.
artiframe> make:class src/Service/EmailService.php
Si no se especifica el directorio de la clase, el comando devolverá un error:
artiframe> make:class
❌ Error: ¡Se debe especificar el directorio para la clase! (ej. /app o /src)
Example: /src/Service/PaymentService
version Comando CLI
Actualiza el número de versión en config/app-version.php de acuerdo a las reglas de versionamiento semántico (SemVer). Formato de versión: MAYOR.MENOR.PARCHE
| Comando | Descripción | Ejemplo |
|---|---|---|
| version upgrade patch | Corrección de errores, mejora menor | 1.2.3 → 1.2.4 |
| version upgrade minor | Nueva característica compatible con versiones anteriores | 1.2.3 → 1.3.0 |
| version upgrade major | Cambio importante que rompe compatibilidad | 1.2.3 → 2.0.0 |
| version downgrade patch | Deshacer el último parche | 1.2.4 → 1.2.3 |
| version downgrade minor | Deshacer el último menor | 1.3.0 → 1.2.0 |
| version downgrade major | Deshacer el último mayor | 2.0.0 → 1.0.0 |
artiframe> version upgrade minor
✔ Versión actualizada a 1.2.0 → 1.3.0.
Ayudantes de Vista (View Helpers) — display() ViewMethod
Aplica una protección XSS obligatoria mientras imprime datos provenientes de la base de datos o entradas del usuario al DOM. En lugar de lanzar un error en valores nulos o vacíos, devuelve un valor predeterminado.
string display(mixed $data, string $default = '')$defaultEl segundo parámetro (
$default) es el texto de rescate que se mostrará en pantalla cuando los datos que provienen de la base de datos estén vacíos (nulo, falso, cadena vacía). Por ejemplo, si utiliza display($nombre, 'Anónimo') para un usuario cuyo nombre no se ha introducido, su página presentará contenido significativo en lugar de horribles espacios en blanco.
<!-- ❌ Inseguro — crea una vulnerabilidad XSS -->
<h1><?= $user['nombre'] ?></h1>
<!-- ✅ Uso Seguro de ArtiFrame -->
<h1><?= display($user['nombre'], 'Usuario Anónimo') ?></h1>
Ayudantes de Vista (View Helpers) — escapeUrl() ViewMethod
Se utiliza al insertar enlaces recibidos de los usuarios (como sitios web de perfil) dentro de un <a href="..."> o <img src="...">. Neutraliza cargas útiles (payloads) maliciosas como javascript:alert(1) (XSS Almacenado) y evita la ejecución de código a través de enlaces.
<!-- ❌ Inseguro — se puede filtrar código JS a través de la URL -->
<a href="<?= $user['website'] ?>">Visitar Sitio</a>
<!-- ✅ Uso Seguro de ArtiFrame -->
<a href="<?= escapeUrl($user['website']) ?>">Visitar Sitio</a>
Ayudantes de Vista (View Helpers) — csrfField() ViewMethod
Añade un campo de token oculto a los formularios HTML contra ataques CSRF. Es obligatorio su uso en todas las páginas que contengan formularios POST.
string csrfField()<form action="/api/guardar.php" method="POST">
<?= csrfField() ?> <!-- Es obligatorio por seguridad -->
<input type="text" name="nombre">
<button type="submit" data-js="btn-guardar">Guardar</button>
</form>
Ayudantes de Vista (View Helpers) — Funciones de Fecha ViewMethod
Procesa cadenas de fecha en formato Y-m-d H:i:s de la base de datos o valores de marca de tiempo (timestamp) de UNIX. Todas las salidas textuales están localizadas según el idioma (tr, en, de, fr, es).
| Función | Ejemplo de Salida | Descripción |
|---|---|---|
| day($date) | 24 | Solo día |
| month($date) | 07 | Solo mes (número) |
| year($date) | 2026 | Año |
| timeOnly($date) | 14:30 | Hora:Minuto |
| fulldate($date) | 24.07.2026 | Fecha completa |
| formatDate($date, $format) | 24.07.2026 14:30 | Formato personalizado |
| monthName($date, $lang) | Julio / July / Juli | Nombre del mes (según el idioma) |
| fulldateName($date, $lang) | 24 de julio de 2026 / July 24, 2026 | Fecha completa (con el nombre del mes, según el idioma) |
| timeAgo($date, $lang) | Hace 5 minutos / 5 minutes ago | Estilo de redes sociales (según el idioma) |
$format y $lang
$format(Formato): Se usa únicamente con la funciónformatDate(). Acepta letras de formato de fecha PHP estándar. (Ejemplo:'d/m/Y'➔ 24/07/2026, o'H:i'➔ 15:30). Permite crear su propio patrón de fecha cuando otras funciones preparadas (day, year, etc.) no son suficientes.$lang(Idioma): Se usa en funciones que contienen texto en su salida (nombre del mes, la palabra "hace"). Si deja este parámetro vacío, el sistema opera por defecto en'tr'(Turco). Si está haciendo un proyecto multilingüe, simplemente ingrese el código del idioma (tr, en, de, fr, es) en el segundo parámetro. (Ej:timeAgo($fecha, 'es')➔ hace 5 minutos)
<!-- Supongamos que es una marca de tiempo UNIX obtenida con la función time() -->
<?php $fecha = time(); ?>
<!-- Fecha completa según el idioma -->
<span><?= fulldateName($fecha, 'es') ?></span>
<!-- Salida: 24 de julio de 2026 -->
<!-- Representación de tiempo estilo redes sociales -->
<span><?= timeAgo($fecha, 'en') ?></span>
<!-- Salida: 5 minutes ago -->
<span><?= timeAgo($fecha, 'de') ?></span>
<!-- Salida: vor 5 Minuten -->
Ayudantes de Vista (View Helpers) — Formateo de Texto ViewMethod
truncate($text, $length, $append)
string truncate(string $text, int $length = 100, string $append = '...')
Recorta textos largos (por ejemplo, resúmenes de blog) en el límite de caracteres deseado sin cortar la última palabra y agrega el sufijo especificado al final.
<p><?= truncate($post['contenido'], 160, '...') ?></p>
Ayudantes de Vista (View Helpers) — money() ViewMethod
Formatea la cantidad con el símbolo de moneda. El código de la moneda (ISO) determina automáticamente la ubicación del símbolo. La moneda por defecto es usd.
| Código | Salida | Moneda |
|---|---|---|
| usd | $1.250,00 | Dólar estadounidense |
| eur | 1.250,00 € | Euro |
| try / tl | 1.250,00 ₺ | Lira Turca |
| gbp | £1.250,00 | Libra Esterlina Británica |
| jpy | 1.250,00 ¥ | Yen Japonés |
| inr | 1.250,00 ₹ | Rupia India |
| rub | 1.250,00 ₽ | Rublo Ruso |
| krw | 1.250,00 ₩ | Won Surcoreano |
| brl | R$1.250,00 | Real Brasileño |
| aed | 1.250,00 د.إ | Dírham de EAU |
<span><?= money($producto['precio'], 'try') ?></span>
<!-- Salida: 1.250,00 ₺ -->
<span><?= money($producto['precio'], 'usd') ?></span>
<!-- Salida: $1.250,00 -->
Ayudantes del Sistema (System Helpers) — jsonResponse() SystemMethod
Configura el encabezado JSON, proporciona el código de estado HTTP y emite la salida de manera segura con json_encode, luego termina el script. Todas las respuestas de API deben darse a través de esta función.
void jsonResponse(array $data, int $statusCode = 200)// Respuesta exitosa
jsonResponse(['status' => 'success', 'data' => $usuario], 200);
// Respuesta de error
jsonResponse(['status' => 'error', 'message' => 'Acceso no autorizado.'], 401);
Ayudantes del Sistema (System Helpers) — verifyCsrf() SystemMethod
Verifica que la solicitud POST entrante proviene de un formulario legítimo. Si no se agregó csrfField() dentro del formulario o si el token no es válido, devuelve false.
if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
jsonResponse(['status' => 'error', 'message' => 'Token CSRF no válido.'], 403);
}
Ayudantes del Sistema (System Helpers) — Funciones de Sanitización SystemMethod
| Función | Descripción |
|---|---|
| sanitizeInt($value) | Elimina todas las letras, símbolos y comas que contiene, dejando solo números enteros. Utilizado para ID o límites. |
| sanitizeFloat($value) | Limpia todo excepto números decimales (fraccionarios). Utilizado para montos de dinero o métricas. |
| sanitizeString($value) | Destruye todas las etiquetas HTML y PHP (<script>, <iframe>, etc.) para evitar ataques XSS y derivados. Deja un texto sin formato y seguro. |
| sanitizeEmail($email) | Filtra todos los caracteres inválidos y peligrosos que no se ajustan al formato de correo electrónico. |
$id = sanitizeInt($_POST['id'] ?? 0);
$nombre= sanitizeString($_POST['nombre'] ?? '');
$email = sanitizeEmail($_POST['email'] ?? '');
Ayudantes del Sistema (System Helpers) — HTTP y Petición SystemMethod
| Función | Descripción |
|---|---|
| isAjax() | Comprueba el encabezado HTTP_X_REQUESTED_WITH para verificar si la solicitud vino de Fetch API / XMLHttpRequest. |
| getClientIp() | Captura de forma segura la dirección IP real del usuario (incluso si está detrás de proxies o Cloudflare). |
| redirect($url) | Crea un encabezado de Location y finaliza de inmediato la ejecución. |
// Bloquear acceso directo desde el navegador al endpoint API
if (!isAjax()) {
jsonResponse(['error' => 'El acceso directo está restringido.'], 403);
}
// Redirigir si no hay sesión
if (!isset($_SESSION['user'])) {
redirect('/login.php');
}
$ip = getClientIp(); // IP real incluso detrás de Cloudflare
Ayudantes del Sistema (System Helpers) — Seguridad SystemMethod
| Función | Descripción |
|---|---|
| generateCsrf() | Genera un nuevo token CSRF y lo guarda en la sesión |
| generateToken($length) | Cadena hex aleatoria segura criptográficamente (restablecimiento de contraseña, clave API, etc.) |
| hashPassword($password) | Hashea la contraseña con bcrypt |
| verifyPassword($password, $hash) | Verifica la contraseña con el hash |
// Generación de token seguro (clave API, enlace de verificación de correo electrónico, etc.)
$token = generateToken(32);
// ⚠️ Nota: Se generan 32 bytes de datos, pero debido a que se convierte al formato
// hexadecimal (base 16), la salida es exactamente el doble, es decir, una cadena de 64 caracteres de largo.
// Registro de contraseña
$hash = hashPassword($_POST['clave']);
// Verificación de contraseña
if (!verifyPassword($_POST['clave'], $usuario['clave_hash'])) {
jsonResponse(['error' => 'Contraseña incorrecta.'], 401);
}
API — Control de Métodos HTTP ApiControl
El arreglo $allowedMethods debe definirse antes del require de ApiControl. Al leer este arreglo, ApiControl rechaza automáticamente las solicitudes de los métodos no permitidos con un 405 Method Not Allowed.
// Un endpoint que solo acepta GET y POST
$allowedMethods = ['GET', 'POST'];
require_once ... . '/../app/ApiControl.php';
// Un endpoint que solo acepta DELETE
$allowedMethods = ['DELETE'];
require_once ... . '/../app/ApiControl.php';
Las solicitudes de preflight OPTIONS devuelven automáticamente 200 para CORS y el script termina — no es necesaria ninguna intervención manual.
API — CORS ApiControl (Comentado)
Si desea permitir el acceso a la API desde otros dominios o aplicaciones móviles, active el bloque CORS en ApiControl.php.
// Dentro de ApiControl.php — se activa eliminando el comentario
header("Access-Control-Allow-Origin: https://su-dominio.com");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
* (todos los dominios) en un entorno de Producción. Especifique explícitamente la dirección del dominio.
API — Limitación de Tasa (Rate Limiting) ApiControl (Comentado)
Para evitar que usuarios malintencionados o bots inunden (flood) la API, puede activar el bloque de Rate Limiting dentro de ApiControl.php. La regla por defecto es: 60 peticiones en 60 segundos por IP.
\Src\Service\RedisService::getInstance(). Antes de descomentar este bloque de código, asegúrese de haber instalado un servicio Redis en su proyecto y haber creado una clase de conexión bajo src/Service/.
// Si hay más de 60 peticiones en 60 segundos de la misma IP:
http_response_code(429); // Demasiadas Peticiones
echo json_encode(['error' => 'Demasiadas peticiones. Por favor, espere.']);
Flujo de Trabajo Completo: Formulario de Contacto (De Extremo a Extremo)
Examinemos paso a paso cómo desarrollar una función de Formulario de Contacto con los estándares de ArtiFrame de principio a fin.
-
Cree el Archivo de Vista (View)
artiframe> make:view contacto.php ✔ public/contacto.php ✔ public/assets/css/contacto.css ✔ public/assets/js/contacto.js -
Cree un Endpoint API
artiframe> make:api standart api/contacto/enviar.php -
Codifique el Formulario HTML
Añada el siguiente formulario al archivopublic/contacto.php:<form action="/api/contacto/enviar.php" method="POST"> <?= csrfField() ?> <input type="text" name="nombre" placeholder="Su Nombre"> <input type="email" name="email" placeholder="Correo Electrónico"> <textarea name="mensaje" placeholder="Su Mensaje"></textarea> <button type="submit" data-js="contacto-enviar">Enviar</button> </form> -
Conecte la API de Fetch con JavaScript
En el archivopublic/assets/js/contacto.js:document.querySelector('[data-js="contacto-enviar"]').addEventListener('click', async (e) => { e.preventDefault(); const formData = new FormData(e.target.closest('form')); const res = await fetch('/api/contacto/enviar.php', { method: 'POST', body: formData }); const data = await res.json(); console.log(data); }); -
Complete la Lógica del Servidor (Backend)
En el archivopublic/api/contacto/enviar.php:<?php $allowedMethods = ['POST']; require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php'; use Bin\SystemMethod; // 1. Validación CSRF if (!verifyCsrf($_POST['csrf_token'] ?? '')) { jsonResponse(['error' => 'Token inválido.'], 403); } // 2. Sanitización de Datos $nombre = sanitizeString($_POST['nombre'] ?? ''); $email = sanitizeEmail($_POST['email'] ?? ''); $mensaje = sanitizeString($_POST['mensaje'] ?? ''); // 3. Lógica de Negocio (Enviar correo, guardar en DB, etc.) // ... // 4. Respuesta jsonResponse(['status' => 'success', 'message' => 'Su mensaje ha sido recibido.'], 200);