🏗️ Módulo 6 - Arquitectura

MVC en PHP desde cero: Arquitectura segura

¿Quieres organizar tu código PHP de forma profesional y mantenible? En esta guía aprenderás a implementar el patrón MVC (Modelo-Vista-Controlador) desde cero, sin frameworks.

⏱️ ~14 min de lectura 🎯 Nivel: Intermedio 🔐 Con enfoque en seguridad

Una buena arquitectura hace que tu código sea modular, mantenible, escalable y seguro. En esta guía aprenderás a implementar MVC en PHP desde cero, ideal para proyectos pequeños y medianos donde necesitas control total.

Principios de arquitectura de software

Una buena arquitectura hace que tu código sea:

  • Modular: partes independientes que se pueden cambiar sin afectar al todo
  • Mantenible: fácil de actualizar y corregir errores
  • Escalable: listo para crecer con nuevas funciones
  • Seguro: menos vulnerabilidades por código desorganizado

¿Qué es el patrón MVC?

MVC divide tu aplicación en tres capas claras que se comunican entre sí:

🗄️ Modelo

Maneja los datos y la lógica de negocio (acceso a base de datos)

🎮 Controlador

Recibe peticiones, procesa lógica y decide qué vista mostrar

👁️ Vista

Muestra la interfaz al usuario (HTML, CSS, presentación)

💡 Consejo profesional

MVC no es un framework, es un patrón de diseño. Puedes implementarlo manualmente o usar frameworks como Laravel que ya lo incluyen.

Estructura de carpetas para MVC en PHP

Crea esta estructura en tu proyecto para mantener el código organizado:

/app
├── /models
│ └── UserModel.php
├── /views
│ └── /users
│ └── index.php
└── /controllers
└── UserController.php
/config.php
/index.php

Implementación paso a paso

1️⃣ config.php: Conexión a la base de datos

<?php
// config.php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "mi_app";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    error_log("Error de conexión: " . $conn->connect_error);
    die("Error de conexión. Intenta más tarde.");
}
?>

2️⃣ Modelo (UserModel.php)

<?php
// app/models/UserModel.php
require_once '../config.php';

class UserModel {
    private $conn;
    
    public function __construct() {
        global $conn;
        $this->conn = $conn;
    }
    
    public function getAllUsers() {
        $sql = "SELECT id, nombre, email FROM usuarios";
        $result = $this->conn->query($sql);
        return $result ? $result->fetch_all(MYSQLI_ASSOC) : [];
    }
    
    public function getUserById($id) {
        $stmt = $this->conn->prepare("SELECT * FROM usuarios WHERE id = ?");
        $stmt->bind_param("i", $id);
        $stmt->execute();
        return $stmt->get_result()->fetch_assoc();
    }
}
?>

3️⃣ Controlador (UserController.php)

<?php
// app/controllers/UserController.php
require_once '../models/UserModel.php';

class UserController {
    private $model;
    
    public function __construct() {
        $this->model = new UserModel();
    }
    
    public function index() {
        $users = $this->model->getAllUsers();
        require '../views/users/index.php';
    }
    
    public function show($id) {
        $user = $this->model->getUserById($id);
        if ($user) {
            require '../views/users/show.php';
        } else {
            http_response_code(404);
            echo "Usuario no encontrado";
        }
    }
}
?>

4️⃣ Vista (views/users/index.php)

<!-- app/views/users/index.php -->
<?php foreach ($users as $user): ?>
  <div class="user-card">
    <h3><?php echo htmlspecialchars($user['nombre']); ?></h3>
    <p><?php echo htmlspecialchars($user['email']); ?></p>
  </div>
<?php endforeach; ?>
⚠️ Nota de seguridad

Siempre usa htmlspecialchars() al mostrar datos del usuario para prevenir ataques XSS. Nunca confíes en datos que vienen de la base de datos.

Seguridad en formularios y prevención de ataques

Con MVC, puedes centralizar la validación y sanitización en el controlador:

🛡️ Ejemplo: Validación en el controlador
// En UserController.php
public function store() {
    // Sanitizar y validar entrada
    $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
    
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $_SESSION['error'] = "Email inválido";
        header("Location: /register");
        exit;
    }
    
    // Hash de contraseña seguro
    $password_hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
    
    // Usar prepared statement para prevenir SQL injection
    $stmt = $this->conn->prepare("INSERT INTO users (email, password) VALUES (?, ?)");
    $stmt->bind_param("ss", $email, $password_hash);
    $stmt->execute();
    
    // Redirigir con mensaje de éxito
    $_SESSION['success'] = "Registro exitoso";
    header("Location: /login");
}

Autenticación segura de usuarios con PHP y MySQLi

Almacena contraseñas con hash seguro y verifica con password_verify():

// Registrar usuario
$password_hash = password_hash($password, PASSWORD_DEFAULT);

// Verificar login
if (password_verify($password_input, $password_hash)) {
    // Contraseña correcta: iniciar sesión
    session_start();
    $_SESSION['user_id'] = $user_id;
    $_SESSION['rol'] = $user_rol;
} else {
    // Contraseña incorrecta
    $_SESSION['error'] = "Credenciales inválidas";
}

Manejo de sesiones en PHP

Usa sesiones para mantener al usuario autenticado entre páginas:

🔐 Ejemplo: Proteger rutas con sesiones
// Al inicio de cada página protegida
session_start();

if (!isset($_SESSION['user_id'])) {
    // Usuario no autenticado: redirigir al login
    header("Location: /login");
    exit;
}

// Usuario autenticado: continuar con la lógica
$user_id = $_SESSION['user_id'];
$rol = $_SESSION['rol'] ?? 'user';

Tip: Regenera el ID de sesión después del login con session_regenerate_id(true) para prevenir fijación de sesiones.

Uso del inspector del navegador para depuración

Aprende a usar las herramientas del navegador (F12) para depurar tu aplicación:

  • Consola: Ver errores de JavaScript y logs con console.log()
  • Network: Analizar peticiones AJAX y respuestas del servidor
  • Application: Inspeccionar cookies, sesiones y almacenamiento local
  • Elements: Depurar problemas de CSS y estructura HTML
🔍 Tip de depuración

Usa var_dump() o print_r() en PHP para ver el contenido de variables, y error_log() para registrar errores sin mostrarlos al usuario.

¿Qué sigue después de MVC?

Este es el sexto paso del desarrollo full-stack. En el siguiente módulo, aprenderás a enviar correos automáticos con PHPMailer, ideal para confirmaciones y recuperación de contraseña.

📚 ¿Quieres la versión completa del libro?

Esta guía es una versión resumida del libro "Programación Full-Stack", que incluye ejercicios resueltos, códigos listos y explicaciones profundas de arquitectura y seguridad.

Comprar en Amazon →

¿Tienes dudas sobre MVC, seguridad o autenticación en PHP? ¡Déjalas en los comentarios! 👇