Skip to content

Tema 4: Implementación de MVC en PHP

4.3 Implementación de MVC en PHP

4.3.1 Preparación del entorno

Para implementar el patrón MVC en PHP, debemos organizar el proyecto en varias carpetas y archivos. La estructura básica será:

/mi-aplicacion-mvc
├── /modelo
│   └── Empleado.php         # Modelo: Interactúa con la base de datos
├── /vista
│   ├── empleados.php        # Vista: Muestra la lista de empleados
│   ├── formulario.php       # Vista: Formulario para agregar/editar empleados
├── /controlador
│   └── EmpleadoControlador.php  # Controlador: Maneja la lógica de las solicitudes
├── index.php                # Archivo principal que recibe las solicitudes
├── bd.php                   # Archivo con la función de conexión a la base de datos
└── /logs                    # Carpeta para los archivos de log (si es necesario)

Explicación de la estructura:

  1. /modelo: Contiene clases que gestionan la interacción con la base de datos.
  2. /vista: Contiene las vistas que muestran los datos al usuario.
  3. /controlador: Gestiona las acciones del usuario, consulta el modelo y pasa los datos a la vista.
  4. index.php: El archivo principal que recibe las solicitudes y las pasa al controlador.
  5. bd.php: Contiene la función de conexión a la base de datos.

4.3.2 El Modelo en MVC (con PDO)

Empezamos creando el archivo bd.php que contendrá la función de conexión a la base de datos utilizando PDO. Este archico se incluirá en todos los scripts que necesiten conectarse a la base de datos.

Este fichero utilza las variables de entorno para obtener los datos de conexión a la base de datos. En nuesto caso están definidaas en el archivo docker-compose.yml de la siguiente manera:

    environment:
      MYSQL_HOST: mysql
      MYSQL_DB: employess
      MYSQL_USER: root
      MYSQL_PASSWORD: root

Realizar las modificaciones necesarias para una conexión correcta a la base de datos.

Ejemplo de conexión a la base de datos: bd.php:

Ejemplo de conexión a la base de datos

<?php
/**
 * Libreriía de conexión con la base de datos
 * Utilizamos PDO
*/

define('DB_HOST_NAME', getenv('MYSQL_HOST') ?? 'localhost');
define('DB_NAME', getenv('MYSQL_DB') ?? 'employess');
define('DB_USER', getenv('MYSQL_USER') ?? 'root');
define('DB_PASSWORD', getenv('MYSQL_PASSWORD') ?? 'root');


class dbConnection {
    private static $instance = null;
    private $conexion;
    private $state = false; 
    private static $ErrorMessage = "";
    private static $ErrorCode = 0;


    private function __construct() {
        try {
            $conn = "mysql:host=" . DB_HOST_NAME . ";dbname=" . DB_NAME;
            $this->conexion = new PDO($conn, DB_USER, DB_PASSWORD);
            $this->conexion->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
            $this->conexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->conexion->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
            $this->state = true;
        } catch (PDOException $e) {
            echo "Error de conexión: " . $e->getMessage();
            self::$ErrorMessage = $e->getMessage();
            self::$ErrorCode = $e->getCode();
            $this->state = false;
        }
    }

    public static function obtenerConexion() {
        if (self::$instance == null) {
            self::$instance = new dbConnection();
        }
        return self::$instance->conexion;
    }
    public function cerrarConexion() {
        $this->conexion = null;
    }

    public static function getState() {
        if (self::$instance == null) {
            return false;
        } 
        return self::$instance->state;
    }

    public static function getErrorMessage() {
        return self::$instance->ErrorMessage;
    }   

    public static function getErrorCode() {
        return self::$instance->ErrorCode;
    }

}
?>

Explicación:

  1. DB_HOST_NAME: Nombre del host de la base de datos.
  2. DB_NAME: Nombre de la base de datos.
  3. DB_USER: Usuario de la base de datos.
  4. DB_PASSWORD: Contraseña de la base de datos.
  5. dbConnection: Clase que maneja la conexión a la base de datos.
  6. obtenerConexion(): Método estático que devuelve la conexión a la base de datos.
  7. cerrarConexion(): Método que cierra la conexión a la base de datos.

Modo de uso:

Ejemplo de uso de la conexión a la base de datos
1
2
3
4
// Incluir el archivo de conexión a la base de datos
include 'bd.php';
// Obtener la conexión a la base de datos
$conexion = dbConnection::obtenerConexion();

Ejemplo de Modelo: Empleado.php:

Ejemplo modelo empleado

<?php
// Incluir el archivo de conexión a la base de datos
include '../bd.php';

class Empleado {
    private $conexion;

    public function __construct() {
        // Obtener la conexión a la base de datos
        $this->conexion = obtenerConexion();
    }

    // Obtener empleados con paginación
    public function obtenerEmpleados($pagina = 1, $empleadosPorPagina = 10) {
        $offset = ($pagina - 1) * $empleadosPorPagina;
        $query = "SELECT * FROM employees LIMIT :empleadosPorPagina OFFSET :offset";
        $stmt = $this->conexion->prepare($query);
        $stmt->bindValue(':empleadosPorPagina', $empleadosPorPagina, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // Obtener un empleado por ID
    public function obtenerEmpleado($emp_id) {
        $query = "SELECT * FROM employees WHERE emp_no = :emp_no";
        $stmt = $this->conexion->prepare($query);
        $stmt->bindValue(':emp_no', $emp_id, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // Crear un nuevo empleado
    public function crearEmpleado($nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero) {
        $query = "INSERT INTO employees (emp_no, first_name, last_name, birth_date, hire_date, gender) 
            VALUES (:id, :nombre, :apellido, :fecha_nacimiento, :fecha_contratacion, :genero)";
        $stmt = $this->conexion->prepare($query);
        $stmt->bindValue(':id', $this->getnewId(), PDO::PARAM_INT);
        $stmt->bindValue(':nombre', $nombre, PDO::PARAM_STR);
        $stmt->bindValue(':apellido', $apellido, PDO::PARAM_STR);
        $stmt->bindValue(':fecha_nacimiento', $fecha_nacimiento, PDO::PARAM_STR);
        $stmt->bindValue(':fecha_contratacion', $fecha_contratacion, PDO::PARAM_STR);
        $stmt->bindValue(':genero', $genero, PDO::PARAM_STR);
        return $stmt->execute();
    }

    // Actualizar un empleado
    public function actualizarEmpleado($emp_id, $nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero) {
        $query = "UPDATE employees 
                SET first_name = :nombre, last_name = :apellido, birth_date = :fecha_nacimiento, 
                    hire_date = :fecha_contratacion, gender = :genero
                WHERE emp_no = :emp_no";
        $stmt = $this->conexion->prepare($query);
        $stmt->bindValue(':emp_no', $emp_id, PDO::PARAM_INT);
        $stmt->bindValue(':nombre', $nombre, PDO::PARAM_STR);
        $stmt->bindValue(':apellido', $apellido, PDO::PARAM_STR);
        $stmt->bindValue(':fecha_nacimiento', $fecha_nacimiento, PDO::PARAM_STR);
        $stmt->bindValue(':fecha_contratacion', $fecha_contratacion, PDO::PARAM_STR);
        $stmt->bindValue(':genero', $genero, PDO::PARAM_STR);
        return $stmt->execute();
    }

    // Eliminar un empleado
    public function eliminarEmpleado($emp_id) {
        $query = "DELETE FROM employees WHERE emp_no = :emp_no";
        $stmt = $this->conexion->prepare($query);
        $stmt->bindValue(':emp_no', $emp_id, PDO::PARAM_INT);
        return $stmt->execute();
    }
}
?>

Explicación:

  1. obtenerEmpleados(): Obtiene empleados con paginación.
    • parámetro $pagina para la paginación.
    • Se utiliza LIMIT y OFFSET para controlar la cantidad de registros devueltos.
    • Se utiliza bindValue para evitar inyecciones SQL.
    • devolvemos un array asociativo con los resultados.
  2. obtenerEmpleado($emp_id): Obtiene un empleado específico por su ID.
    • Parámetro $emp_id para identificar al empleado.
    • Se utiliza bindValue para evitar inyecciones SQL.
    • devolvemos un array asociativo con los resultados.
  3. crearEmpleado(): Inserta un nuevo empleado en la base de datos.
    • Parámetros para los datos del empleado.
    • Se utiliza bindValue para evitar inyecciones SQL.
    • devolvemos true si la inserción fue exitosa.
  4. actualizarEmpleado(): Actualiza un empleado existente.
    • Parámetros para el ID del empleado y los nuevos datos.
    • Se utiliza bindValue para evitar inyecciones SQL.
    • devolvemos true si la actualización fue exitosa.
  5. eliminarEmpleado(): Elimina un empleado de la base de datos.
    • Parámetro $emp_id para identificar al empleado.
    • Se utiliza bindValue para evitar inyecciones SQL.
    • devolvemos true si la eliminación fue exitosa.

4.3.3 La Vista en MVC

La Vista es la que presenta los datos al usuario. Aquí creamos la vista empleados.php, que muestra la lista de empleados.

Ejemplo de Vista: empleados.php:

Ejemplo vista empleados

<?php
// Incluir el controlador
include '../controlador/EmpleadoControlador.php';

// Crear una instancia del controlador
$controlador = new EmpleadoControlador();

// Obtener la lista de empleados
$empleados = $controlador->verEmpleados();
?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Empleados</title>
</head>
<body>
    <h1>Lista de Empleados</h1>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Nombre</th>
                <th>Apellido</th>
                <th>Fecha de Nacimiento</th>
                <th>Fecha de Contratación</th>
                <th>Género</th>
                <th>Acciones</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($empleados as $empleado): ?>
            <tr>
                <td><?= $empleado['emp_no'] ?></td>
                <td><?= $empleado['first_name'] ?></td>
                <td><?= $empleado['last_name'] ?></td>
                <td><?= $empleado['birth_date'] ?></td>
                <td><?= $empleado['hire_date'] ?></td>
                <td><?= $empleado['gender'] ?></td>
                <td>
                    <a href="empleadosForm.php?id=<?= $empleado['emp_no'] ?>">Editar</a>
                    <button onclick="eliminarEmpleado(<?= $empleado['emp_no']?>);" >Eliminar</button>
                </td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
    <a href="empleadosForm.php">Agregar Nuevo Empleado</a>
    <script>
        function eliminarEmpleado(emp_no) {
            if (confirm('¿Estás seguro de que deseas eliminar este empleado?')) {
                window.location.href = `empleadosForm.php?id=${emp_no}&action=delete`;
            }
        }
    </script>
</body>
</html>

Explicación:

  1. Esta vista muestra la lista de empleados en una tabla.
  2. Los empleados se obtienen a través del Controlador y se pasan a la Vista.
  3. El enlace de editar y el botón de eliminar permiten que el usuario interactúe con la aplicación.

Formulario para la creación/edición de empleados:

Formulario empleados

<?php
// Incluir el controlador
include '../controlador/EmpleadoControlador.php';

// Crear una instancia del controlador
$controlador = new EmpleadoControlador();

// Inicializar las variables
$empleado = null;
$modo = 'crear';  // Por defecto, estamos en modo 'crear'

// Verificar si estamos editando un empleado
if (isset($_GET['id'])) {
    $emp_id = $_GET['id'];  // Obtener el ID del empleado a editar
    $empleado = $controlador->verEmpleado($emp_id);
    $modo = 'editar';  // Establecer el modo a 'editar'
}

if (isset($_GET['action']) && $_GET['action'] == 'delete') {
    // Eliminar el empleado
    $modo = 'eliminar';  // Establecer el modo a 'eliminar'
}

// Si se recibe un formulario POST, procesar la creación o actualización
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    // Obtener los datos del formulario
    $nombre = $_POST['nombre'];
    $apellido = $_POST['apellido'];
    $fecha_nacimiento = $_POST['fecha_nacimiento'];
    $fecha_contratacion = $_POST['fecha_contratacion'];
    $genero = $_POST['genero'];

    if ($modo == 'editar') {
        // Actualizar el empleado
        $controlador->editarEmpleado($emp_id, $nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero);
    } else if ($modo == 'crear') {
        // Crear un nuevo empleado
        $controlador->agregarEmpleado($nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero);
    } else {
        $controlador->eliminarEmpleado($emp_id);
    }

    // Redirigir a la lista de empleados
    header('Location: empleados.php');
    exit;
}
?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Formulario Empleado</title>
</head>
<body>
    <h1><?= $modo != 'eliminar' ? 'Editar' : 'Eliminar' ?> Empleado</h1>
    <form method="POST">
        <label for="nombre">Nombre:</label>
        <input type="text" name="nombre" value="<?= $empleado['first_name'] ?? '' ?>" required><br>

        <label for="apellido">Apellido:</label>
        <input type="text" name="apellido" value="<?= $empleado['last_name'] ?? '' ?>" required><br>

        <label for="fecha_nacimiento">Fecha de Nacimiento:</label>
        <input type="date" name="fecha_nacimiento" value="<?= $empleado['birth_date'] ?? '' ?>" required><br>

        <label for="fecha_contratacion">Fecha de Contratación:</label>
        <input type="date" name="fecha_contratacion" value="<?= $empleado['hire_date'] ?? '' ?>" required><br>

        <label for="genero">Género:</label>
        <select name="genero" required>
            <option value="M" <?= isset($empleado['gender']) && $empleado['gender'] == 'M' ? 'selected' : '' ?>>Masculino</option>
            <option value="F" <?= isset($empleado['gender']) && $empleado['gender'] == 'F' ? 'selected' : '' ?>>Femenino</option>
        </select><br>

        <button type="submit"><?= $modo != 'eliminar' ? 'Guadar' : 'Eliminar' ?></button>
        <a href="empleados.php">Cancelar</a>
    </form>
</body>
</html>

Explicación:

  1. Verificación de modo:

    • Si el parámetro id está presente en la URL, el formulario entra en modo de edición o modo de eliminación. Para poder distinguir entre edición y eliminación se ha añadido un parámetro más que es action que en el caso de eliminación tendrá el valodr delete. En ambos casos se cargan los datos del empleado en el formulario.

    • Si no hay id, se asume que el formulario está en modo de creación y se dejará vacío para que el usuario lo complete.

  2. Pre-rellenado de campos:

    • Los campos del formulario se pre-rellenan con los valores actuales del empleado si estamos editando. Si no, se dejan vacíos para la creación de un nuevo empleado.
  3. Acción POST:

    • Si el formulario se envía, se verifica el modo. Si es editar, se llama a la función de actualización. Si es crear, se llama a la función de creación. Y si no es ninguno de los anteriores, se llama a la función de eliminación.
  4. Redirección:

    • Después de guardar o actualizar el empleado, redirigimos al usuario a la página de empleados.php.

4.3.4 El Controlador en MVC

El Controlador gestiona las solicitudes de la Vista, interactúa con el Modelo y actualiza la Vista con los datos.

Ejemplo de Controlador: EmpleadoControlador.php:

Ejemplo controlador EmpleadoControlador

<?php
// Incluir el modelo
include '../modelo/Empleado.php';

class EmpleadoControlador {
    private $modelo;

    public function __construct() {
        $this->modelo = new Empleado();
    }

    // Ver empleados
    public function verEmpleados($pagina = 1) {
        return $this->modelo->obtenerEmpleados($pagina);
    }

    // Obtener un empleado por ID
    public function verEmpleado($emp_id) {
        return $this->modelo->obtenerEmpleado($emp_id);
    }

    // Agregar un nuevo empleado
    public function agregarEmpleado($nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero) {
        return $this->modelo->crearEmpleado($nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero);
    }

    // Editar un empleado existente
    public function editarEmpleado($emp_id, $nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero) {
        return $this->modelo->actualizarEmpleado($emp_id, $nombre, $apellido, $fecha_nacimiento, $fecha_contratacion, $genero);
    }

    // Eliminar un empleado
    public function eliminarEmpleado($emp_id) {
        return $this->modelo->eliminarEmpleado($emp_id);
    }
}
?>

Explicación:

  1. verEmpleados(): Obtiene la lista de empleados con paginación.

    • Parámetro $pagina para la paginación.
    • Llama al método del Modelo para obtener los empleados.
    • Devuelve la lista de empleados a la Vista.
  2. verEmpleado($emp_id): Muestra los detalles de un empleado específico.

    • Parámetro $emp_id para identificar al empleado.
    • Llama al método del Modelo para obtener un empleado por su ID.
    • Devuelve los detalles del empleado a la Vista.
  3. agregarEmpleado(): Llama al Modelo para agregar un nuevo empleado.

    • Parámetros para los datos del nuevo empleado.
    • Llama al método del Modelo para crear un nuevo empleado.
    • Devuelve true si la inserción fue exitosa.
  4. editarEmpleado(): Llama al Modelo para editar un empleado existente.

    • Parámetros para el ID del empleado y los nuevos datos.
    • Llama al método del Modelo para actualizar un empleado.
    • Devuelve true si la actualización fue exitosa.
  5. eliminarEmpleado(): Llama al Modelo para eliminar un empleado.
    • Parámetro $emp_id para identificar al empleado.
    • Llama al método del Modelo para eliminar un empleado.
    • Devuelve true si la eliminación fue exitosa.

Resumen del punto 4.3:

  1. Modelo: Gestiona la interacción con la base de datos usando PDO y maneja la lógica de negocio.
  2. Vista: Presenta los datos al usuario y separa la lógica de presentación.
  3. Controlador: Gestiona las acciones del usuario, consulta el Modelo y actualiza la Vista.