Crear CRUD Módulo de Personas con LegoBox PHP MySQL

En este articulo te voy a mostrar, paso a paso, cómo está construido el módulo de ejemplo que trae LegoBox v5 — un CRUD completo de Personas con PHP y MySQL — para que puedas usarlo como plantilla en tu propio proyecto.

En el artículo anterior presenté LegoBox PHP, el micro-framework MVC que uso desde 2016 como base de mis sistemas en producción.

La idea es simple: si entiendes cómo funciona este módulo, ya sabes cómo construir cualquier otro. Modelo, servicio, rutas y vistas siguen siempre el mismo patrón.

Listado de Personas en LegoBox PHP
Listado de Personas en LegoBox PHP

Estructura del módulo Personas

El módulo de personas utiliza la arquitectura desacoplada del framework:

lb-min-5/
├── index.php                         # Definición de rutas y handlers
├── core/app/model/PersonData.php     # Modelo de datos (Extiende de LbModel)
├── core/app/service/PersonService.php # Lógica de negocio (App\Service\PersonService)
└── public/persons/                   # Vistas HTML en Twig 3
    ├── index.html.twig               # Listado principal
    ├── new.html.twig                 # Formulario de creación
    ├── edit.html.twig                # Formulario de edición
    └── show.html.twig                # Detalle de la persona

Cuatro capas, cuatro responsabilidades: el modelo habla con la base de datos, el servicio contiene las reglas de negocio, las rutas conectan la petición HTTP con el servicio, y las vistas en Twig muestran el resultado. Nada se mezcla.

Paso 1: Definición de la tabla SQL

Todo empieza en schema.sql, donde se define la tabla person:

create table person(
    id int not null auto_increment primary key,
    name varchar(50),
    lastname varchar(50),
    email varchar(255),
    address varchar(255),
    phone varchar(255),
    image varchar(255),
    created_at datetime
);

Esta tabla se importa automáticamente al instalar el framework, así que no hay que escribirla a mano para empezar a probar.

Paso 2: El modelo de datos

El modelo representa la tabla y hereda de Extra (que a su vez hereda de LbModel, el mini-ORM sobre PDO):

<?php

class PersonData extends Extra {
    public static $tablename = "person";

    public $id, $name, $lastname, $email, $phone, $created_at, $address, $image;

    public function __construct(){
        parent::__construct();
        $this->name = "";
        $this->lastname = "";
        $this->email = "";
        $this->created_at = "NOW()";
    }
}

Con solo heredar de LbModel, PersonData obtiene gratis los métodos que vas a usar todo el tiempo:

  • PersonData::all() — obtener todos los registros.
  • PersonData::find($id) — buscar por ID.
  • $person->save() — crear o actualizar un registro.
  • $person->delete() — eliminar un registro.

Este es el punto donde LegoBox te ahorra más código repetido: no hay que escribir el INSERT, el UPDATE ni el SELECT a mano para cada módulo nuevo.

Paso 3: La capa de servicio

El servicio encapsula las reglas de negocio e interactúa con el modelo. Aquí es donde vive la lógica, no en el controlador ni en la vista:

<?php
namespace App\Service;

/**
 * Clase PersonService
 * Lógica de negocio y operaciones CRUD para el módulo de Personas.
 */
class PersonService {
    /**
     * Obtiene el listado completo de personas
     */
    public function getAllPersons(): array {
        return \PersonData::all();
    }

    /**
     * Obtiene una persona por su ID
     */
    public function getPersonById(int $id) {
        return \PersonData::find($id);
    }

    /**
     * Crea una nueva persona
     */
    public function createPerson(array $data): bool {
        $person = new \PersonData();
        $person->name = $data['name'] ?? '';
        $person->lastname = $data['lastname'] ?? '';
        $person->email = $data['email'] ?? '';
        $person->phone = $data['phone'] ?? '';
        $person->address = $data['address'] ?? '';
        
        return $person->save();
    }

    /**
     * Actualiza una persona existente
     */
    public function updatePerson(int $id, array $data): bool {
        $person = \PersonData::find($id);
        if (!$person) return false;

        $person->name = $data['name'] ?? $person->name;
        $person->lastname = $data['lastname'] ?? $person->lastname;
        $person->email = $data['email'] ?? $person->email;
        $person->phone = $data['phone'] ?? $person->phone;
        $person->address = $data['address'] ?? $person->address;

        return $person->save();
    }

    /**
     * Elimina una persona
     */
    public function deletePerson(int $id): bool {
        $person = \PersonData::find($id);
        if (!$person) return false;

        return $person->delete();
    }
}

Fíjate que el servicio nunca toca SQL directamente — todo pasa por PersonData. Si mañana cambias de motor de base de datos o agregas una capa de caché, este archivo casi no se toca.

Paso 4: Registro de rutas y handlers en index.php

Con el modelo y el servicio listos, falta conectar las peticiones HTTP. Esto se hace con FastRoute y un switch de handlers:

// 1. Registro de rutas en FastRoute
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/persons', 'list_persons');
    $r->addRoute('GET', '/person/new', 'show_new_person');
    $r->addRoute('POST', '/person/create', 'process_create_person');
    $r->addRoute('GET', '/person/{id:\d+}', 'show_person');
    $r->addRoute('GET', '/person/{id:\d+}/edit', 'show_edit_person');
    $r->addRoute('POST', '/person/{id:\d+}/update', 'process_update_person');
    $r->addRoute('POST', '/person/{id:\d+}/delete', 'process_delete_person');
});

// 2. Handlers en el switch principal
$personService = new PersonService();

switch ($handler) {
    case 'list_persons':
        $persons = $personService->getAllPersons();
        ViewEngine::render('persons/index.html.twig', ['persons' => $persons]);
        break;

    case 'show_new_person':
        ViewEngine::render('persons/new.html.twig');
        break;

    case 'process_create_person':
        $errors = Request::validate([
            'name' => 'required',
            'lastname' => 'required'
        ]);
        if (!empty($errors)) {
            Session::flash('error', implode(' ', $errors));
            header('Location: ' . $baseFolder . '/person/new');
            exit;
        }
        $personService->createPerson(Request::post());
        Session::flash('success', 'Persona registrada correctamente.');
        header('Location: ' . $baseFolder . '/persons');
        break;

    case 'show_person':
        $person = $personService->getPersonById((int)$vars['id']);
        ViewEngine::render('persons/show.html.twig', ['person' => $person]);
        break;

    case 'show_edit_person':
        $person = $personService->getPersonById((int)$vars['id']);
        ViewEngine::render('persons/edit.html.twig', ['person' => $person]);
        break;

    case 'process_update_person':
        $personService->updatePerson((int)$vars['id'], Request::post());
        Session::flash('success', 'Persona actualizada correctamente.');
        header('Location: ' . $baseFolder . '/persons');
        break;

    case 'process_delete_person':
        $personService->deletePerson((int)$vars['id']);
        Session::flash('info', 'Persona eliminada correctamente.');
        header('Location: ' . $baseFolder . '/persons');
        break;
}

Nota el uso de Request::validate() en la creación: es la validación integrada de LegoBox, así que no hay que escribir el chequeo de campos requeridos a mano. Y los Session::flash() son los mensajes de un solo uso que verás aparecer después de cada redirección.

Paso 5: Las vistas del Modulo Personas en Twig

Por último, las vistas — completamente desacopladas de la lógica, viviendo en public/persons/.

Listado de Personas (public/persons/index.html.twig)

{% extends "layouts/main.html.twig" %}

{% block title %}Lista de Personas{% endblock %}

{% block content %}
<div class="card shadow-sm border-0">
    <div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
        <h4 class="mb-0 text-primary fw-bold">Listado de Personas</h4>
        <a href="{{ base_url }}/person/new" class="btn btn-success fw-semibold">+ Nueva Persona</a>
    </div>
    <div class="card-body p-0">
        <div class="table-responsive">
            <table class="table table-striped table-hover mb-0 align-middle">
                <thead class="table-light">
                    <tr>
                        <th class="ps-3">ID</th>
                        <th>Nombre</th>
                        <th>Email</th>
                        <th>Teléfono</th>
                        <th class="text-center">Acciones</th>
                    </tr>
                </thead>
                <tbody>
                {% for person in persons %}
                    <tr>
                        <td class="ps-3 fw-bold">{{ person.id }}</td>
                        <td>{{ person.name }} {{ person.lastname }}</td>
                        <td>{{ person.email }}</td>
                        <td>{{ person.phone }}</td>
                        <td class="text-center">
                            <a href="{{ base_url }}/person/{{ person.id }}" class="btn btn-sm btn-info text-white">Ver</a>
                            <a href="{{ base_url }}/person/{{ person.id }}/edit" class="btn btn-sm btn-warning">Editar</a>
                            <form action="{{ base_url }}/person/{{ person.id }}/delete" method="POST" class="d-inline" onsubmit="return confirm('¿Seguro que deseas eliminar esta persona?');">
                                <button type="submit" class="btn btn-sm btn-danger">Eliminar</button>
                            </form>
                        </td>
                    </tr>
                {% else %}
                    <tr>
                        <td colspan="5" class="text-center py-4 text-muted">No hay personas registradas.</td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>
{% endblock %}

Nueva Persona (public/persons/new.html.twig)

Formulario para dar de alta una persona en LEgoBox PHP
Formulario para dar de alta una persona en LEgoBox PHP

Ahora el formulario para crear o dar de alta a una persona en el sistema.

{% extends "layouts/main.html.twig" %}

{% block title %}Nueva Persona{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card shadow-sm border-0">
            <div class="card-header bg-primary text-white py-3 d-flex justify-content-between align-items-center">
                <h4 class="mb-0 fw-bold">Agregar Nueva Persona</h4>
                <a href="{{ base_url }}/persons" class="btn btn-light btn-sm text-primary fw-semibold">Volver</a>
            </div>
            <div class="card-body p-4">
                <form action="{{ base_url }}/person/create" method="POST">
                    <div class="row mb-3">
                        <div class="col-md-6">
                            <label for="name" class="form-label">Nombre</label>
                            <input type="text" name="name" id="name" class="form-control" required>
                        </div>
                        <div class="col-md-6">
                            <label for="lastname" class="form-label">Apellido</label>
                            <input type="text" name="lastname" id="lastname" class="form-control" required>
                        </div>
                    </div>
                    <div class="row mb-3">
                        <div class="col-md-6">
                            <label for="email" class="form-label">Email</label>
                            <input type="email" name="email" id="email" class="form-control">
                        </div>
                        <div class="col-md-6">
                            <label for="phone" class="form-label">Teléfono</label>
                            <input type="text" name="phone" id="phone" class="form-control">
                        </div>
                    </div>
                    <div class="mb-3">
                        <label for="address" class="form-label">Dirección</label>
                        <textarea name="address" id="address" class="form-control" rows="2"></textarea>
                    </div>
                    <button type="submit" class="btn btn-success">Guardar Persona</button>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

Las vistas de edición y detalle (edit.html.twig y show.html.twig) siguen exactamente el mismo patrón que new.html.twig, solo que precargan los datos del registro ({{ person.name }}, {{ person.email }}, etc.) en lugar de mostrar campos vacíos.

Descargar

Este módulo de Personas ya viene incluido en LegoBox PHP v5 — no necesitas armarlo desde cero para probarlo.

Descarga el framework completo, con el módulo de autenticación y este CRUD de ejemplo listos para usar, en el artículo de presentación de LegoBox PHP, donde también están el repositorio en GitHub y el enlace de descarga directa.

Probar el módulo

  1. Iniciar sesión: entra a http://localhost/lb-min-5/login (usuario admin, contraseña admin).
  2. Listado: navega a http://localhost/lb-min-5/persons.
  3. Crear persona: presiona + Nueva Persona y llena el formulario. Al guardar, recibirás un mensaje flash de confirmación.
  4. Editar/eliminar: usa los botones correspondientes en la tabla de acciones.

De aquí en adelante

Este módulo de Personas es literalmente una plantilla: copia las cuatro piezas (tabla, modelo, servicio, rutas y vistas), cambia los nombres y campos, y en minutos tienes un CRUD nuevo funcionando — sea para productos, clientes, citas o lo que necesite tu sistema.

Te invito a ver Inventio Max, mi sistema de inventario y ventas multi-sucursal construido sobre LegoBox PHP.

¿Tienes dudas sobre cómo adaptar este módulo a tu propio proyecto? Déjamelas en los comentarios.

Leave a Reply

Your email address will not be published. Required fields are marked *

Discover more from Evilnapsis

Subscribe now to keep reading and get access to the full archive.

Continue reading