MVC PHP un Ejemplo Práctico
Ejemplo práctico de MVC en PHP con una aplicación de gestión de notas . Usaremos PDO y Login aplicando una buena estructura, organización y mantenibilidad del código.
INDICE
- 1. ¿Qué es la Arquitectura MVC?
- 2. Componentes del MVC Explicados
- 3. ¿Cómo Funciona MVC en PHP?
- 4. Ventajas de Usar MVC en tus proyectos PHP
- 5. Gestor de Notas Colaborativo (con Autenticación y Búsqueda)
- 6. Paso 1: Estructura el Proyecto e Ingredientes
- 7. Paso 2: Configuración de la Base de Datos (Paso Inicial Crítico)
- 8. Paso 3: El Modelo User – El Corazón de los Datos
- 9. Paso 4: El Modelo Note con buscador
- 10. Paso 5: Los Controllers – El Director de Orquesta
- 11. Paso 6: Las Vistas – Lo Que Ve el Usuario
- 12. Paso 7: El Front Controller – El Enrutador Central (index.php)
- 13. Prueba, Debug y Mejoras
- Descargar fuente código 8.47 KB
¿Quieres escribir código PHP más organizado, escalable y fácil de mantener? La arquitectura Modelo-Vista-Controlador (MVC) es tu respuesta. Este patrón de diseño, ampliamente adoptado en el desarrollo web, separa la lógica de negocio, la presentación y el control de una aplicación, transformando proyectos desordenados en sistemas estructurados y profesionales. En este tutorial, descubrirás qué es MVC, cómo funciona en PHP y cómo implementarlo paso a paso con un ejemplo práctico. ¡Prepárate para llevar tus habilidades al siguiente nivel!
¿Qué es la Arquitectura MVC?
¿Qué es la Arquitectura MVC?
La Arquitectura MVC es un patrón de diseño de software que separa la lógica de una aplicación en tres componentes interconectados, promoviendo la modularidad y la mantenibilidad. Surgió en los años 70 para interfaces gráficas, pero hoy es un estándar en desarrollo web. En esencia, MVC divide el código para que cada parte tenga una responsabilidad única: manejar datos, mostrar información o procesar interacciones del usuario. Esto evita el "spaghetti code" (código desorganizado) y facilita el trabajo en equipo, ya que un desarrollador puede enfocarse en la vista sin tocar la lógica de negocio.
Componentes del MVC Explicados
MVC se compone de tres pilares fundamentales:
- Model (Modelo): Representa los datos y la lógica de negocio. Es el "cerebro" que interactúa con la base de datos, archivos o APIs externas. Por ejemplo, en una app de tareas, el Model se encarga de guardar, recuperar o eliminar entradas de la BD. No sabe nada de la interfaz; solo maneja datos puros.
- View (Vista): Es la capa de presentación. Aquí va todo lo visual: HTML, CSS y algo de JavaScript para la UI. Su rol es mostrar los datos del Model al usuario de forma atractiva y responsive. No procesa lógica; solo "pinta" lo que recibe.
- Controller (Controlador): Actúa como intermediario. Recibe las solicitudes del usuario (ej. un clic en "Agregar Tarea"), consulta al Model para obtener o modificar datos, y luego selecciona la View adecuada para renderizar la respuesta. Es el "director" que orquesta el flujo.
Estos componentes se comunican unidireccionalmente: Usuario → Controller → Model/View, y Model → View (vía Controller).
¿Cómo Funciona MVC en PHP?
En PHP, MVC se implementa de forma nativa gracias a su flexibilidad, aunque frameworks como Laravel o Symfony lo facilitan. El flujo típico es:
- Entrada del Usuario: Una petición HTTP (GET/POST) llega al "front controller" (un archivo como index.php).
- Enrutamiento: El Controller analiza la URL o parámetros (ej. ?action=add) y decide qué hacer.
- Procesamiento: El Controller invoca métodos del Model para leer/escribir datos.
- Renderizado: El Controller pasa los datos a la View, que genera el HTML dinámico.
- Respuesta: El navegador recibe el output y lo muestra.
Por ejemplo, al cargar una página, PHP interpreta el Controller, que consulta la BD vía Model y "incluye" la View con require. Esto mantiene el código limpio y escalable, especialmente en apps con rutas complejas.
Ventajas de Usar MVC en tus proyectos PHP
Adoptar MVC no es solo una moda; trae beneficios reales:
- Separación de Responsabilidades: Facilita el debugging y las pruebas unitarias. Cambia la UI sin romper la lógica.
- Reutilización y Escalabilidad: Modelos se pueden compartir entre vistas; Controllers manejan múltiples acciones.
- Colaboración en Equipo: Diseñadores tocan Views, backend devs los Models/Controllers.
- Mantenibilidad: Código organizado reduce errores a largo plazo. En PHP, previene mezclas de SQL/HTML que generan vulnerabilidades como inyecciones.
- Facilita el Testing: Puedes mockear componentes (ej. simular una BD en pruebas).
- Mejor Rendimiento: En apps grandes, carga solo lo necesario, optimizando recursos.
En resumen, MVC transforma proyectos caóticos en estructuras profesionales. ¡Ideal para PHP, donde el procedural es común pero el estructurado brilla!
Ahora, ¡manos a la obra! Te traigo un ejemplo práctico y sencillo: una mini-aplicación para gestionar tareas (To-Do List). Usaremos solo PHP puro, sin frameworks, para que veas los conceptos claros.
Gestor de Notas Colaborativo (con Autenticación y Búsqueda)
Gestor de Notas Colaborativo (con Autenticación y Búsqueda)
¿Por qué esto? Es más versátil (útil para equipos remotos), incorpora features avanzadas como login (sessions), CRUD completo (incluyendo Update), búsqueda con LIKE en SQL, y compartición vía enlaces. Todo en PHP puro + MVC, sin frameworks pesados. Mismo setup inicial (XAMPP, MySQL), pero con upgrades que te enseñan conceptos pro: hashing de contraseñas, validaciones, y queries optimizadas.
Requisitos Previos
- Servidor local: XAMPP (con PHP 7+ y MySQL).
- Editor: VS Code o similar.
- Conocimientos básicos de PHP y SQL.
Paso 1: Estructura el Proyecto e Ingredientes
Paso 1: Estructura el Proyecto e Ingredientes
MVC vive de la organización. Crea esta estructura de carpetas:
note-app/
├── config/ # Configuraciones globales (BD)
│ └── database.php
├── models/ # Lógica de datos (clases para User y Note)
│ ├── User.php
│ └── Note.php
├── views/ # Plantillas HTML (UI)
│ ├── login.php
│ ├── dashboard.php
│ ├── note-form.php
│ └── note-view.php
├── controllers/ # Procesadores de acciones (lógica del flujo)
│ ├── AuthController.php
│ └── NoteController.php
└── index.php # Punto único de entrada (enruta todo)
Por qué esta estructura? Todo pasa por index.php (front controller). Evita URLs feas como login.php?do=stuff. Si agregas más features, solo extiendes el switch.
Inicia XAMPP (Apache + MySQL). En phpMyAdmin (http://localhost/phpmyadmin), ejecuta:
Paso 2: Configuración de la Base de Datos (Paso Inicial Crítico)
Paso 2: Configuración de la Base de Datos (Paso Inicial Crítico)
Qué hacemos: Creamos la BD y tablas. Sin esto, la app crashea con errores de conexión.
Por qué MySQL/PDO? MySQL es gratis y común en XAMPP. PDO (PHP Data Objects) es seguro (previene inyecciones SQL) y portable (cambia de MySQL a PostgreSQL fácil).
Código SQL (ejecuta en phpMyAdmin, pestaña "SQL"):
CREATE DATABASE note_db; -- Crea la BD principal USE note_db; -- La selecciona para las tablas-- Tabla users: Almacena logins. UNIQUE evita duplicados. CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, -- ID auto-generado, clave primaria username VARCHAR(50) UNIQUE NOT NULL, -- Nombre único, obligatorio password VARCHAR(255) NOT NULL, -- Hash largo para seguridad email VARCHAR(100) UNIQUE NOT NULL, -- Email único created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Fecha auto );-- Tabla notes: Almacena notas. FOREIGN KEY borra notas si borras usuario. CREATE TABLE notes ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, -- Título corto content TEXT NOT NULL, -- Contenido largo (texto libre) priority ENUM('low', 'medium', 'high') DEFAULT 'medium', -- Opciones fijas, default media user_id INT NOT NULL, -- Dueño (referencia a users.id) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -- Borra notas si usuario se va );
Archivo config/database.php (conexión segura):
$host = 'localhost'; // Servidor MySQL en XAMPP
$dbname = 'note_db'; // Nombre de nuestra BD
$username = 'root'; // Usuario default XAMPP
$password = ''; // Vacío en XAMPP local
try { // Bloque try-catch para manejar errores
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Error de conexión: " . $e->getMessage());
}
Verificación: En phpMyAdmin, ve a note_db > tablas. Debe haber users y notes
Este bloque de código PHP es un mecanismo estándar y seguro para establecer la conexión a una base de datos MySQL utilizando la extensión PDO (PHP Data Objects).
A continuación, se explica cada parte:
Configuración de Parámetros
Al inicio, se definen las credenciales y la ubicación de la base de datos en variables simples.
| Variable | Valor | Explicación |
$host |
'localhost' |
La dirección del servidor de la base de datos. Para entornos locales como XAMPP, suele ser localhost. |
$dbname |
'note_db' |
El nombre específico de la base de datos que tu aplicación va a utilizar (en este caso, note_db). |
$username |
'root' |
El nombre de usuario que se utiliza para acceder a MySQL. En una instalación predeterminada de XAMPP/MAMP, este es comúnmente root. |
$password |
'' |
La contraseña de ese usuario. En una instalación predeterminada de XAMPP/MAMP, la contraseña de root está vacía. |
⚠️ Nota de Seguridad: En un entorno de producción (servidor real),
$host,$username, y$passwordtendrían valores diferentes y mucho más seguros.
Conexión Segura con PDO (Try-Catch)
El bloque try...catch es crucial, ya que intenta realizar la conexión y proporciona una forma controlada de manejar cualquier fallo (como credenciales incorrectas o servidor caído).
Bloque try (Intento de Conexión)
PHP
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
-
Instanciación de PDO:
-
$pdo = new PDO(...)crea una nueva instancia del objeto PDO. Este objeto se convertirá en tu manejador de la base de datos. -
El primer argumento es el DSN (Data Source Name):
"mysql:host=$host;dbname=$dbname". Esto le indica a PDO que se conecte a un servidor MySQL (mysql:) ubicado en$hosty que use la base de datos$dbname. -
Los siguientes argumentos son el
$usernamey la$password.
-
-
Configuración de Modo de Error:
-
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);es una práctica de seguridad esencial. -
Le dice a PDO que, si ocurre un error en la base de datos (por ejemplo, una sintaxis SQL incorrecta o una columna que no existe), debe lanzar una excepción (
PDOException). Esto detiene inmediatamente la ejecución y permite que el bloquecatchmaneje el problema, evitando que los detalles del error SQL se muestren al usuario final (lo cual podría ser un riesgo de seguridad).
-
Bloque catch (Manejo de Errores)
PHP
catch (PDOException $e) {
die("Error de conexión: " . $e->getMessage());
}
-
Si ocurre cualquier error durante el bloque
try(principalmente fallos de conexión), se captura como un objeto$ede tipoPDOException. -
die(...)detiene la ejecución del script y muestra un mensaje de error genérico. -
$e->getMessage()proporciona el detalle técnico del error (útil para la depuración), pero dado que este código se detiene condie(), solo se muestra al desarrollador si este archivo es accedido directamente. En un entorno real, es mejor registrar este error en un log en lugar de mostrarlo.
El archivo config/database.php tiene el único propósito de crear y exponer la variable $pdo, un objeto que está correctamente configurado y listo para ejecutar consultas seguras (generalmente sentencias preparadas) en la base de datos.
Paso 3: El Modelo User – El Corazón de los Datos
Paso 3: El Modelo User – El Corazón de los Datos
Qué son Models? Clases que encapsulan operaciones BD (get, create, etc.). No saben de UI ni usuarios – solo datos puros.
Por qué clases? Reutilizables. Llama $note->getAll() desde cualquier Controller sin repetir SQL.
models/User.php (Maneja auth):
Estructura Principal y Conexión
1. Requerimiento de Archivo y Propiedad Privada
| Código | Explicación |
require_once 'config/database.php'; |
Incluye el archivo que establece la conexión a la base de datos y expone el objeto $pdo (el manejador de PDO). |
private $pdo; |
Declara una propiedad privada dentro de la clase para almacenar el objeto $pdo. Esto asegura que la conexión solo sea accesible dentro de esta clase. |
2. Función Constructora: __construct($pdo)
| Función | Propósito | Explicación |
__construct |
Inicializa el objeto User. |
Cuando creas una instancia de la clase ($user = new User($pdo);), se le debe pasar el objeto PDO. Esta función guarda ese objeto ($pdo) en la propiedad privada $this->pdo, permitiendo que todos los demás métodos de la clase interactúen con la base de datos. |
Funciones de Gestión de Usuarios
Función: register($username, $email, $password)
| Función | Propósito | Explicación |
register |
Crea un nuevo usuario en la tabla users. |
1. Hashing de Contraseña: Utiliza md5($password, PASSWORD_DEFAULT) para generar un hash de la contraseña. Nota: El código usa md5(), que es obsoleto y peligroso para contraseñas. El comentario PASSWORD_DEFAULT parece ser un error de sintaxis y debe ser password_hash($password, PASSWORD_DEFAULT). 2. Sentencia Preparada: Prepara una consulta INSERT INTO para agregar los datos. 3. Ejecución: Ejecuta la consulta usando los parámetros ($username, $email, $hashed), previniendo inyección SQL. |
Función: login($username, $password)
| Función | Propósito | Explicación |
login |
Verifica las credenciales del usuario. | 1. Búsqueda: Busca al usuario en la BD por su $username. 2. Fetch: Obtiene la fila del usuario ($user) como un array asociativo. 3. Hashing de la Entrada: Genera el hash MD5 de la contraseña proporcionada por el usuario ($password_md5). 4. Verificación: Compara si el $user existe Y si el hash recién generado ($password_md5) coincide con el hash almacenado en la BD ($user['password']). 5. Resultado: Devuelve el array del usuario si es exitoso, o false si falla. |
Función: getById($id)
| Función | Propósito | Explicación |
getById |
Recupera un usuario por su ID. | 1. Búsqueda: Prepara una consulta SELECT * filtrando por el ID. 2. Ejecución: Ejecuta la consulta con el $id. 3. Resultado: Devuelve los datos del usuario como un array asociativo. Es útil para funcionalidades como compartir notas (saber a qué usuario corresponde un ID específico). |
Paso 4: El Modelo Note con buscador
Paso 4: El Modelo Note con buscador
🛠️ Estructura y Conexión
Requerimiento y Constructor
| Código | Explicación |
require_once 'config/database.php'; |
Incluye el archivo que inicializa la conexión a la base de datos y proporciona el objeto $pdo (el manejador de la conexión segura). |
private $pdo; |
Propiedad privada para almacenar el objeto de conexión $pdo, asegurando que solo los métodos de esta clase puedan usarlo. |
public function __construct($pdo) |
Constructor que recibe el objeto $pdo como argumento y lo asigna a la propiedad $this->pdo. Esto es un buen patrón de diseño llamado Inyección de Dependencias. |
🚀 Funciones CRUD y Lógica de Negocio
1. getAll($userId, $search = '') - Listar y Buscar
Esta función es responsable de obtener todas las notas de un usuario, con la funcionalidad adicional de poder aplicar una búsqueda opcional.
| Código Clave | Propósito |
SELECT * FROM notes WHERE user_id = ? |
Consulta base: Selecciona todas las notas que pertenecen al usuario actual ($userId). |
if ($search) |
Bloque condicional que se ejecuta solo si se proporciona un término de búsqueda. |
AND (title LIKE ? OR content LIKE ?) |
Si hay búsqueda, añade una cláusula AND que filtra los resultados para que el término de búsqueda aparezca en el title O en el content. |
$searchTerm = "%$search%"; |
Envuelve el término de búsqueda con comodines (%) para buscar coincidencias parciales (ej: buscar "doc" en "documento importante"). |
$stmt->fetchAll(PDO::FETCH_ASSOC); |
Devuelve el resultado como un array de arrays asociativos, donde cada subarray es una nota. |
2. create($title, $content, $priority, $userId) - Crear
Esta función inserta una nueva nota en la base de datos.
| Código Clave | Propósito |
INSERT INTO notes (...) VALUES (?, ?, ?, ?) |
Consulta para añadir una nueva fila. |
return $stmt->execute(...) |
Ejecuta la sentencia preparada pasando los valores. Devuelve true si la inserción fue exitosa y false en caso contrario. |
3. update($id, $title, $content, $priority) - Actualizar
Esta función modifica una nota existente.
| Código Clave | Propósito |
UPDATE notes SET title = ?, content = ?, priority = ? WHERE id = ? |
Consulta para modificar los campos de una nota específica, identificada por su $id. |
| Nota de Seguridad: | Falta la verificación de $userId en el WHERE. Un atacante podría actualizar la nota de otro usuario si conoce el $id. Es altamente recomendable añadir AND user_id = ? a la cláusula WHERE (como se hace en delete y getById). |
4. delete($id, $userId) - Borrar
Esta función elimina una nota específica.
| Código Clave | Propósito |
DELETE FROM notes WHERE id = ? AND user_id = ? |
Consulta para eliminar. |
| Doble Verificación: | Es fundamental que esta función incluya AND user_id = ?. Esto asegura que un usuario solo puede borrar sus propias notas y no las notas de otros, incluso si conoce el ID. |
5. getById($id, $userId) - Obtener por ID
Esta función recupera los datos de una única nota.
| Código Clave | Propósito |
SELECT * FROM notes WHERE id = ? AND user_id = ? |
Busca una nota específica por su ID. |
| Doble Verificación: | Al igual que en delete, la condición AND user_id = ? garantiza que el usuario solo pueda ver sus propias notas y evita el acceso no autorizado a información de otros usuarios. |
$stmt->fetch(PDO::FETCH_ASSOC); |
Devuelve solo una fila de resultado (la nota), o false si la nota no se encuentra o no pertenece al usuario. |
Paso 5: Los Controllers – El Director de Orquesta
Paso 5: Los Controllers – El Director de Orquesta
Nos enfocaremos en dos controladores clave:
- AuthController: Para manejar el login, logout y verificación de sesiones.
- NoteController: Para operaciones CRUD (Crear, Leer, Actualizar, Eliminar) de notas, con protección de rutas y búsqueda.
Implementando AuthController (Autenticación)
El AuthController es el guardián de tu app. Maneja el login y verifica si el usuario está logueado.
Código Completo de AuthController
Guarda esto en controllers/AuthController.php:
PHP
class AuthController {
private $userModel;
public function __construct($pdo) {
$this->userModel = new User($pdo);
}
public function login() {
$error = '';
if ($_POST) {
$user = $this->userModel->login($_POST['username'], $_POST['password']);
if ($user) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
exit;
} else {
$error = "Credenciales inválidas";
}
}
require 'views/login.php';
}
public function logout() {
session_destroy();
header('Location: index.php?action=login');
exit;
}
public function isLoggedIn() {
return isset($_SESSION['user_id']);
}
}
Explicación Paso a Paso
- Constructor (__construct):
- Recibe $pdo (conexión PDO a BD).
- Instancia User para validar credenciales. Tip: En User::login(), usa password_verify() para hashear contraseñas.
- Método login():
- Detecta $_POST para procesar el formulario.
- Valida con el model; si OK, guarda en $_SESSION y redirige (PRG).
- Si falla, pasa $error a la vista login.php (muestra form con mensaje).
- Ejemplo de vista básica: Un <form> con inputs para username/password y <?php echo $error; ?>.
- Método logout():
- Limpia la sesión y redirige. Simple y efectivo.
- Método isLoggedIn():
- Helper para chequear $_SESSION['user_id']. Úsalo en otros controllers para proteger rutas.
Prueba esto: En index.php, si $_GET['action'] == 'login', instancia y llama $auth->login().
Implementando NoteController (Gestión de Notas)
Ahora, el corazón de la app: crear, ver, editar y borrar notas. Integra auth para solo usuarios logueados.
Código Completo de NoteController
Guarda en controllers/NoteController.php:
PHP
class NoteController {
private $noteModel;
private $auth;
public function __construct($pdo, $auth) {
$this->noteModel = new Note($pdo);
$this->auth = $auth;
}
public function dashboard() {
if (!$this->auth->isLoggedIn()) header('Location: index.php?action=login');
$userId = $_SESSION['user_id'];
$search = $_GET['search'] ?? '';
$notes = $this->noteModel->getAll($userId, $search);
require 'views/dashboard.php';
}
public function createOrUpdate($id = null) {
if (!$this->auth->isLoggedIn()) header('Location: index.php?action=login');
$userId = $_SESSION['user_id'];
$note = $id ? $this->noteModel->getById($id, $userId) : null;
if (!$note && $id) header('Location: index.php?action=dashboard');
if ($_POST) {
$title = trim($_POST['title']);
$content = trim($_POST['content']);
$priority = $_POST['priority'];
if ($id) {
$this->noteModel->update($id, $title, $content, $priority);
} else {
$this->noteModel->create($title, $content, $priority, $userId);
}
header('Location: index.php?action=dashboard');
exit;
}
require 'views/note-form.php';
}
public function delete($id) {
if (!$this->auth->isLoggedIn()) header('Location: index.php?action=login');
$this->noteModel->delete($id, $_SESSION['user_id']);
header('Location: index.php?action=dashboard');
exit;
}
public function view($id) {
if (!$this->auth->isLoggedIn()) header('Location: index.php?action=login');
$note = $this->noteModel->getById($id, $_SESSION['user_id']);
if (!$note) header('Location: index.php?action=dashboard');
require 'views/note-view.php';
}
}
Explicación Paso a Paso
- Constructor (__construct):
- Recibe $pdo y $auth (inyección: crea AuthController antes y pásalo).
- Tip: En index.php: $auth = new AuthController($pdo); $notes = new NoteController($pdo, $auth);.
- Método dashboard() (Lista de Notas):
- Protege con isLoggedIn().
- Obtiene notas con búsqueda: $_GET['search'] ?? '' (PHP 7+ null coalescing).
- Llama Note::getAll() (recuerda la query con OR para notas compartidas).
- Carga dashboard.php con $notes (usa un <table> o lista para mostrar).
- Método createOrUpdate($id = null) (Crear/Editar – ¡Reutilizable!):
- Protege ruta.
- Si $id: Carga nota existente (verifica owner).
- Si $_POST: Limpia inputs con trim(), llama model (create/update), redirige (PRG).
- Carga note-form.php (form con fields title, content, priority; prellenar si edit).
- Método delete($id):
- Protege y llama Note::delete() (model verifica permisos).
- Redirige.
- Método view($id):
- Protege y carga nota (verifica acceso).
- Muestra en note-view.php (e.g., <h1><?php echo $note['title']; ?></h1>).
Paso 6: Las Vistas – Lo Que Ve el Usuario
Paso 6: Las Vistas – Lo Que Ve el Usuario
Qué son? HTML con PHP embebido (). Reciben variables de Controller (ej. $notes). Solo presentación, no lógica compleja.
views/login.php (Pantalla inicial):
<?php include_once("views/layout/header.php") ?>
<h1>Inicia Sesión</h1>
<?php if (isset($error)): ?>
<p style="color: red;"><?php echo $error; ?></p>
<?php endif; ?>
<form method="POST" action=""> <!-- POST al mismo index.php (enrutado por action=login) -->
<label>Usuario: <input type="text" name="username" required></label> <br>
<label>Contraseña: <input type="password" name="password" required></label> <br>
<button type="submit">Entrar</button>
</form>
<p><em>Prueba con: admin / password123</em></p>
<?php include_once("views/layout/footer.php") ?>
views/dashboard.php (Lista principal con búsqueda):
<?php include_once("views/layout/header.php") ?>
<h1>GESTOR DE NOTAS</h1>
<h2>Bienvenido, <?php echo $_SESSION['username']; ?>!
<a href="index.php?action=logout" style="float: right;">Salir</a></h2>
<form method="GET" style="margin-bottom: 20px;">
<input type="hidden" name="action" value="dashboard">
<input type="text" name="search" placeholder="Buscar en título o contenido..." value="<?php echo $search ?? ''; ?>">
<button type="submit">Buscar</button>
<a href="index.php?action=create" style="margin-left: 10px;">+ Nueva Nota</a>
</form>
<?php if (empty($notes)): ?>
<p>No hay notas. ¡Crea una!</p>
<?php else: ?>
<?php foreach ($notes as $note): ?>
<div class="note <?php echo $note['priority']; ?>">
<h3><a href="index.php?action=view&id=<?php echo $note['id']; ?>"><?php echo $note['title']; ?></a></h3>
<p><?php echo substr($note['content'], 0, 100); ?>...</p>
<small>Prioridad: <?php echo ucfirst($note['priority']); ?> | <?php echo $note['created_at']; ?></small><br>
<a href="index.php?action=edit&id=<?php echo $note['id']; ?>">Editar</a> |
<a href="index.php?action=delete&id=<?php echo $note['id']; ?>" onclick="return confirm('¿Seguro que quieres eliminar?')">Eliminar</a>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?php include_once("views/layout/footer.php") ?>
views/note-form.php (Form para nueva/edit):
<?php include_once("views/layout/header.php") ?>
<h1><?php echo $note ? 'Editar Nota' : 'Nueva Nota'; ?></h1>
<form method="POST" action=""> <!-- POST para datos sensibles -->
<label>Título: <input type="text" name="title" value="<?php echo $note ? $note['title'] : ''; ?>" required maxlength="255"></label><br>
<label>Contenido: <textarea name="content" required><?php echo $note ? $note['content'] : ''; ?></textarea></label><br>
<label>Prioridad:
<select name="priority"> <!-- Dropdown para opciones fijas -->
<option value="low" <?php echo ($note && $note['priority']=='low') ? 'selected' : ''; ?>>Baja</option>
<option value="medium" <?php echo ($note && $note['priority']=='medium') ? 'selected' : ''; ?>>Media</option>
<option value="high" <?php echo ($note && $note['priority']=='high') ? 'selected' : ''; ?>>Alta</option>
</select>
</label><br>
<button type="submit"><?php echo $note ? 'Actualizar' : 'Crear'; ?></button>
<a href="index.php?action=dashboard">← Volver al Dashboard</a>
</form>
<?php include_once("views/layout/footer.php") ?>
views/note-view.php (Detalle de nota):
<?php include_once("views/layout/header.php") ?>
<h1><?php echo $note['title']; ?></h1>
<p><strong>Prioridad:</strong> <?php echo ucfirst($note['priority']); ?></p>
<div class="content"><?php echo $note['content']; ?></div>
<p><small>Creado: <?php echo $note['created_at']; ?> </small></p>
<a href="index.php?action=dashboard">← Volver</a>
<?php include_once("views/layout/footer.php") ?>
Paso 7: El Front Controller – El Enrutador Central (index.php)
Paso 7: El Front Controller – El Enrutador Central (index.php)
Qué es? Archivo único que recibe TODAS requests. Analiza URL params y delega.
Explicación Detallada del Router Principal (index.php)
Este archivo PHP sirve como el router central o punto de entrada único de la aplicación MVC (Model-View-Controller). Su función principal es:
- Inicializar la sesión y cargar dependencias (BD, controladores).
- Instanciar los controladores con inyección de dependencias.
- Leer el parámetro action de la URL (e.g., index.php?action=dashboard).
- Usar un switch para mapear la acción a métodos específicos de los controladores, delegando la lógica (procesamiento, validaciones, vistas).
- Manejar casos por defecto y redirecciones para una navegación segura y evitar errores.
Esto crea un flujo limpio: URL → Acción → Controlador → Vista o Redirección. Sigue el patrón PRG (Post-Redirect-Get) para prevenir reenvíos de formularios y protege rutas implícitamente (las verificaciones de sesión están en los controladores). Es escalable: solo agrega más case para nuevas rutas.
A continuación, desgloso el código sección por sección, con explicaciones, flujos y tips.
1. Inicialización de Sesión y Carga de Dependencias
PHP
<?php
session_start();
require_once 'config/database.php';
require_once 'controllers/AuthController.php';
require_once 'controllers/NoteController.php';
- session_start();:
- Inicia (o reanuda) la sesión PHP. Esto es esencial para variables como $_SESSION['user_id'] usadas en autenticación. Debe ir al principio para evitar warnings ("headers already sent").
- Flujo: Si no existe sesión, la crea; si sí, la recupera.
- require_once 'config/database.php';:
- Incluye el archivo de configuración de BD, que típicamente define constantes (e.g., host, user, pass) y crea/retorna la conexión PDO ($pdo = new PDO(...)).
- Por qué require_once: Evita recargas múltiples si el archivo se incluye en loops o includes anidados. Si falta, genera fatal error.
- require_once 'controllers/AuthController.php'; y require_once 'controllers/NoteController.php';:
- Carga las clases de controladores. Asume que están en la carpeta controllers/.
- Tip: En producción, usa un autoloader (e.g., Composer) para evitar requires manuales.
Propósito de esta sección: Prepara el entorno global. Sin esto, los controladores fallarían al instanciarse.
2. Instanciación de Controladores con Inyección de Dependencias
PHP
$auth = new AuthController($pdo);
$noteController = new NoteController($pdo, $auth);
- $auth = new AuthController($pdo);:
- Crea el objeto de autenticación, pasando la conexión PDO. El constructor del AuthController usa $pdo para instanciar User (modelo) y manejar logins.
- Beneficio: Centraliza la auth; reutilizable en el switch.
- $noteController = new NoteController($pdo, $auth);:
- Crea el controlador de notas, inyectando PDO (para modelo Note) y el objeto $auth (para isLoggedIn()).
- Inyección de dependencias: Desacopla código – el NoteController no "conoce" cómo crear auth, lo recibe. Facilita tests (mockea $auth).
Propósito: Objetos listos para usar en el enrutamiento. Se instancian una vez por request, no por acción (eficiente).
3. Lectura del Parámetro de Acción
PHP
$action = $_GET['action'] ?? 'login';
- $_GET['action'] ?? 'login':
- Extrae el valor de action de la query string (e.g., ?action=edit&id=5 → 'edit').
- Si no existe (URL limpia como index.php), usa 'login' como fallback (operador null coalescing, PHP 7+).
- Seguridad: $_GET es seguro aquí (no datos sensibles), pero sanitiza si usas en queries.
Propósito: Define la "ruta". Fácil de debuggear: var_dump($action); para ver qué se ejecuta.
4. Enrutamiento Principal: Estructura del Switch
PHP
switch ($action) {
// ... cases ...
}
- Estructura general: Un switch evalúa $action y ejecuta bloques. Cada case delega a un método del controlador, que maneja:
- Verificación de sesión (protegida).
- Procesamiento de POST/GET.
- Llamadas a modelos (BD).
- Carga de vistas o redirecciones.
- Comportamiento común: No hay output directo aquí; todo se resuelve en controladores. Si un método redirige (header('Location: ...') + exit), el switch se detiene.
Ahora, detallo cada case:
- case 'login': $auth->login(); break;
- Delega al AuthController::login().
- Flujo: Si POST, valida credenciales y redirige a dashboard; sino, carga vista login.php con posible error.
- Uso: Acceso inicial o fallback.
- case 'logout': $auth->logout(); break;
- Llama AuthController::logout().
- Flujo: Destruye sesión y redirige a login. Simple cierre de sesión.
- case 'dashboard': $noteController->dashboard(); break;
- Llama NoteController::dashboard().
- Flujo: Verifica login; obtiene notas + búsqueda de $_GET['search']; carga dashboard.php con $notes.
- Uso: Panel principal post-login.
- case 'create': $noteController->createOrUpdate(); break;
- Llama createOrUpdate(null) (sin ID → modo create).
- Flujo: Verifica login; si POST, crea nota y redirige; sino, carga form note-form.php.
- Reutilización: Mismo método para create/edit.
- case 'edit':
PHP
$id = $_GET['id'] ?? null; if ($id) $noteController->createOrUpdate($id); else header('Location: index.php?action=dashboard'); break;- Extrae ID de URL (e.g., ?action=edit&id=3).
- Si ID válido: createOrUpdate($id) (modo edit: carga nota, procesa updates).
- Sino: Redirige a dashboard (evita errores).
- Seguridad: ID verificado en controlador (propiedad de nota).
- case 'delete':
PHP
$id = $_GET['id'] ?? null; if ($id) $noteController->delete($id); else header('Location: index.php?action=dashboard'); break;- Similar: Si ID, borra (modelo verifica permisos) y redirige; sino, a dashboard.
- Tip: En vistas, usa onclick="return confirm('¿Borrar?');" para UX.
- case 'view':
PHP
$id = $_GET['id'] ?? null; if ($id) $noteController->view($id); else header('Location: index.php?action=dashboard'); break;- Carga detalle de nota si ID válido; sino, redirige.
- Flujo: Verifica acceso en controlador.
- default:
PHP
if ($auth->isLoggedIn()) { header('Location: index.php?action=dashboard'); } else { header('Location: index.php?action=login'); }- Guardián global: Para acciones inválidas o URLs malformadas.
- Si logueado: A dashboard (evita accesos no intencionales).
- Sino: A login (protege la app).
- Sin break: No needed, fin del switch.
Propósito del switch: Centraliza rutas. Cada case es atómico; redirecciones mantienen URLs limpias.
Prueba, Debug y Mejoras
Prueba, Debug y Mejoras
Cómo probar (paso a paso):
- Inicia XAMPP (Apache/MySQL).
- http://localhost/note-app/index.php: Login screen. Usa admin/password123.
- Loguea: Va a dashboard (vacío).
- Nueva Nota: Crea con prioridad alta, comparte con ID 1 (self para test).
- Dashboard: Busca "alta" – filtra. Edita, ve detalle, borra (confirma JS).
- Logout: Vuelve a login.
Leido 424 veces | 3 usuarios
MVC PHP un Ejemplo Práctico
Accede al código fuente esencial de nuestra aplicación en formato ZIP ó TXT. Ideal para desarrolladores que desean personalizar o integrar nuestra solución.
- [ Pago: Paypal ]
- [ Precio: USD 7.00 ]
- [ Descargas: 6 ]
CÓDIGO FUENTE: USD 7.00
Conversar con J.Luis
