Apuntes DAM
Tema 4 de 7DWES · 2º DAW

Desarrollo Web en Entorno Servidor

POO y el Patrón MVC

Código espagueti frente a separación de responsabilidades, POO en PHP 8 (promoción, readonly, enums, interfaces), Composer y autoload PSR-4, el patrón MVC con controlador frontal, plantillas y frameworks como Laravel y Symfony.

60 min lecturaIntermedio

1. El problema del código espagueti

Los primeros scripts de PHP suelen mezclarlo todo en un mismo fichero: la consulta a la base de datos, las comprobaciones, los cálculos y el HTML, intercalados. Funciona mientras la página es pequeña, pero en cuanto crece aparecen los problemas: la misma consulta copiada en diez sitios, un cambio de diseño que obliga a tocar la lógica, y un código que nadie se atreve a modificar. A esto se le llama cariñosamente código espagueti.

La solución es separar responsabilidades: que cada parte del código se ocupe de una sola cosa. Para eso PHP ofrece programación orientada a objetos, y sobre ella se construye el patrón más usado en aplicaciones web: Modelo-Vista-Controlador.

2. Programación orientada a objetos en PHP

Si ya conoces la POO de Java, la de PHP te resultará muy familiar: clases, visibilidad (public, protected, private), herencia, interfaces, clases abstractas y excepciones. PHP 8 añadió además varias comodidades que hacen las clases mucho más cortas:

src/Modelo/Producto.php
1<?php
2declare(strict_types=1);
3
4namespace App\Modelo;
5
6use InvalidArgumentException;
7
8final class Producto
9{
10    public function __construct(
11        public readonly ?int $id,
12        public readonly string $nombre,
13        public readonly float $precio,
14        private int $stock = 0,
15    ) {
16        if ($precio < 0) {
17            throw new InvalidArgumentException('El precio no puede ser negativo');
18        }
19    }
20
21    public function hayStock(int $cantidad = 1): bool
22    {
23        return $this->stock >= $cantidad;
24    }
25
26    public function retirar(int $cantidad): void
27    {
28        if (!$this->hayStock($cantidad)) {
29            throw new StockInsuficiente($this->nombre);
30        }
31        $this->stock -= $cantidad;
32    }
33}
CaracterísticaQué aporta
Promoción de propiedades en el constructorDeclarar y asignar las propiedades en la propia lista de parámetros
readonlyLa propiedad solo se asigna una vez: objetos inmutables sin escribir getters
Tipos en propiedades, parámetros y retornoErrores detectados al momento en lugar de datos corruptos
enumUn conjunto cerrado de valores: enum Estado: string { case Pendiente = 'pendiente'; … }
Interfaces y traitsContratos que cumplen varias clases, y bloques de código reutilizables entre clases
Espacios de nombres (namespace)Organizar las clases en carpetas sin choques de nombres

Composer y el autoload

Composer es el gestor de dependencias de PHP, el equivalente a npm o Maven. Además de instalar librerías, genera un autoloader: siguiendo el estándar PSR-4, relaciona cada espacio de nombres con una carpeta y carga cada clase la primera vez que se usa. Así desaparecen las listas interminables de require.

composer.json
1{
2  "require": { "php": ">=8.2", "twig/twig": "^3.0" },
3  "autoload": { "psr-4": { "App\\": "src/" } }
4}

3. El patrón Modelo-Vista-Controlador

MVC divide la aplicación en tres papeles. Lo importante no es el nombre de las carpetas, sino la regla que hay detrás: cada parte solo conoce lo que necesita. La vista no sabe de bases de datos, el modelo no sabe de HTML y el controlador coordina sin hacer el trabajo de los otros dos.

MVC en una biblioteca

Modelo
El archivo y sus normas: qué libros hay, quién puede llevárselos y durante cuánto tiempo.
Controlador
El bibliotecario: atiende tu petición, consulta el archivo y decide qué respuesta darte.
Vista
La ficha que te entrega, con un formato cuidado. No decide nada, solo presenta.
  1. Todas las peticiones llegan a un único fichero, public/index.php, el controlador frontal.
  2. El router mira el método y la ruta (GET /productos/7) y elige qué controlador y qué método ejecutar.
  3. El controlador lee los datos de la petición, pide al modelo lo que necesita y elige una vista.
  4. El modelo aplica las reglas de negocio y accede a los datos (normalmente a través de un repositorio).
  5. La vista recibe los datos ya preparados y genera el HTML (o el JSON).
src/Controlador/ProductoControlador.php
1final class ProductoControlador
2{
3    public function __construct(
4        private ProductoRepositorio $productos,
5        private Vista $vista,
6    ) {}
7
8    public function mostrar(int $id): string
9    {
10        $producto = $this->productos->buscar($id);
11        if ($producto === null) {
12            http_response_code(404);
13            return $this->vista->render('errores/404');
14        }
15        return $this->vista->render('productos/ficha', ['producto' => $producto]);
16    }
17}
plantillas/productos/ficha.php
1<h1><?= e($producto->nombre) ?></h1>
2<p class="precio"><?= number_format($producto->precio, 2, ',', '.') ?> €</p>
3<?php if ($producto->hayStock()): ?>
4  <form method="post" action="/carrito"><button>Añadir al carrito</button></form>
5<?php else: ?>
6  <p>Agotado</p>
7<?php endif ?>

Fíjate en que la plantilla no hace consultas ni cálculos de negocio: solo muestra. La función e() es un atajo habitual para htmlspecialchars. Motores de plantillas como Twig (Symfony) o Blade (Laravel) van más allá: escapan todo automáticamente, permiten heredar un diseño común y prohíben meter lógica compleja en la vista.

¿Qué es MVC? - Aprende MVC en 10 minutos! — The Coder Cave | Programación y Tecnología

4. Frameworks: MVC ya construido

Escribir tu propio router, tu sistema de plantillas y tu capa de datos es un ejercicio excelente para entender cómo funcionan las cosas. En un proyecto real, en cambio, se usa un framework que ya resuelve todo eso y además la validación, la autenticación, las migraciones de base de datos, las colas o los tests.

routes/web.php (Laravel)
1Route::get('/productos/{producto}', [ProductoController::class, 'show']);
2
3// app/Http/Controllers/ProductoController.php
4public function show(Producto $producto)
5{
6    return view('productos.ficha', ['producto' => $producto]);   // Laravel busca el producto por su id
7}
FrameworkFilosofíaIdeal para
LaravelProductividad: convenciones, Eloquent, Blade y un ecosistema enormeLa mayoría de aplicaciones; el más demandado en España
SymfonyComponentes desacoplados y configurables, Doctrine y TwigProyectos grandes y de larga duración
Slim / LumenMicroframeworks: router y poco másAPIs pequeñas

Aprende primero sin framework

Los frameworks esconden mucha magia. Si has construido un MVC sencillo a mano, entenderás qué hace Laravel por debajo y sabrás qué buscar cuando algo falle. Por eso en clase se suele empezar sin framework.

5. Retos

El primer reto es un modelo que protege sus propias reglas; el segundo, el corazón de cualquier motor de plantillas: sustituir variables escapando su contenido.

Retos de PHP en el IDE

Escribe el código que falta y pulsa Ejecutar tests: se ejecuta con PHP 8.3 real y se prueba con varios casos, algunos ocultos.

🐘PHPUna entidad que se valida solaMedio

Completa la clase Producto: el constructor usa promoción de propiedades (string $nombre, float $precio, int $stock = 0) y lanza InvalidArgumentException con «El nombre es obligatorio», «El precio no puede ser negativo» o «El stock no puede ser negativo». precioConIva() devuelve el precio con el 21 %. Al convertirlo en texto (__toString) debe dar «Teclado: 24,99 € (30,24 € con IVA) · stock 5». El programa ya lee líneas «nombre;precio;stock» y muestra el producto o «Error: mensaje».

⏳
Productos válidos
⏳
Errores de validación
⏳
Test oculto #3
0/3 tests pasados
🐘PHPUn motor de plantillas mínimoDifícil

Separar la presentación de la lógica es la base de MVC. Completa render($plantilla, $datos): sustituye cada {{ clave }} por el valor de $datos escapado con htmlspecialchars, y {{ clave|raw }} por el valor sin escapar. Las claves pueden tener puntos para entrar en arrays anidados (usuario.nombre). Si una clave no existe, se sustituye por una cadena vacía. La primera línea de la entrada son los datos en JSON y el resto la plantilla.

⏳
Variables simples
⏳
Escapado, raw y anidados
⏳
Test oculto #3
0/3 tests pasados

¿Has terminado este tema?

Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

Guardar mi progreso