Proyecto web en PHP orientado a objetos: tienda de cuencos tibetanos

En este proyecto vamos a desarrollar una aplicación web completa utilizando PHP orientado a objetos, MySQL, HTML y CSS. La temática será una tienda online de cuencos tibetanos llamada Sonido Interior.

Este proyecto es una evolución del proyecto inicial realizado con PHP básico. En la primera versión trabajamos con páginas PHP sencillas, formularios, includes, conexión directa a base de datos y consultas SQL escritas en cada archivo.

En esta nueva versión vamos a dar un paso más importante: organizaremos el código mediante clases, separaremos responsabilidades y construiremos una estructura más cercana a la que se utiliza en aplicaciones web reales.

El objetivo no es complicar el proyecto sin necesidad, sino aprender a programar de forma más ordenada, reutilizable y mantenible.


1. Objetivo general del proyecto

Vamos a crear una tienda web de cuencos tibetanos con una parte pública y una parte administrativa.

La parte pública permitirá ver la página de inicio, consultar el catálogo de productos y visualizar información de la tienda.

La parte administrativa permitirá iniciar sesión, añadir productos, listar productos, editar productos y borrar productos.

La diferencia principal respecto a la versión anterior será la forma de organizar el código.

En lugar de escribir toda la lógica directamente en archivos como producto-guardar.php, producto-editar.php o catalogo.php, crearemos clases encargadas de realizar esas tareas.

Por ejemplo, tendremos clases como:

  • Producto
  • Categoria
  • Usuario
  • Conexion
  • ProductoDAO
  • CategoriaDAO
  • UsuarioDAO

Con esto empezaremos a trabajar una arquitectura más limpia y profesional.


2. Qué significa usar programación orientada a objetos

La programación orientada a objetos, o POO, es una forma de programar basada en clases y objetos.

Una clase es como un molde. Define qué datos tendrá un elemento y qué acciones podrá realizar.

Un objeto es una instancia concreta de una clase.

Por ejemplo, en nuestro proyecto podemos tener una clase Producto.

Esa clase representa cualquier producto de la tienda.

class Producto {
private $idProducto;
private $nombre;
private $descripcion;
private $precio;
private $stock;
private $imagen;
}

Después, un cuenco tibetano concreto sería un objeto de esa clase.

$producto = new Producto();

La ventaja de este enfoque es que el código queda mejor organizado. En lugar de trabajar con variables sueltas y consultas repartidas por muchas páginas, agrupamos la información y el comportamiento en clases.


3. Diferencia entre la versión básica y la versión POO

En la versión básica del proyecto, la conexión a la base de datos y las consultas pueden estar escritas directamente dentro de las páginas.

Por ejemplo:

include("includes/conexion.php");

$sql = "SELECT * FROM productos";
$resultado = $conexion->query($sql);

En la versión orientada a objetos, intentaremos que la página no tenga que saber cómo se hace la consulta.

La página pedirá los productos a una clase especializada:

$productoDAO = new ProductoDAO();
$productos = $productoDAO->obtenerTodos();

La página solo se encargará de mostrar los datos. La clase ProductoDAO será la encargada de hablar con la base de datos.

Esto hace que el proyecto sea más fácil de mantener, ampliar y corregir.


4. Tecnologías que vamos a utilizar

En esta versión seguiremos utilizando las mismas tecnologías principales:

  • HTML.
  • CSS.
  • PHP.
  • MySQL o MariaDB.
  • Apache.
  • phpMyAdmin.
  • Hosting o contenedor Docker.

La diferencia es que en PHP trabajaremos con:

  • Clases.
  • Objetos.
  • Atributos.
  • Métodos.
  • Constructores.
  • Encapsulación.
  • Getters y setters.
  • Clases DAO.
  • Separación de responsabilidades.
  • Consultas preparadas.
  • Sesiones.
  • Autoload básico o includes de clases.

5. Resultado final esperado

Al finalizar esta versión del proyecto tendremos una aplicación web parecida a la anterior, pero con una organización interna mucho mejor.

La aplicación incluirá:

  • Página de inicio.
  • Catálogo público de productos.
  • Página de login.
  • Zona administrativa.
  • Alta de productos.
  • Listado administrativo.
  • Edición de productos.
  • Borrado lógico de productos.
  • Subida de imagen por producto.
  • Conexión a base de datos mediante una clase.
  • Entidades representadas mediante clases.
  • Clases DAO para acceder a la base de datos.
  • Uso de sesiones para proteger la administración.
  • Código más limpio y reutilizable.

Fases del proyecto

Fase 1: repaso de la versión inicial

Antes de empezar con la versión orientada a objetos, revisaremos brevemente la versión anterior del proyecto.

Recordaremos cómo funcionaba:

  • Las páginas públicas.
  • Las páginas administrativas.
  • Los formularios.
  • Los includes.
  • La conexión a la base de datos.
  • Las consultas SELECT, INSERT, UPDATE y DELETE.
  • La subida de imágenes.
  • El login con sesiones.

Esto es importante porque la versión POO no cambia la finalidad del proyecto. Lo que cambia es la forma de organizar el código.


Fase 2: análisis de las clases necesarias

El primer paso será pensar qué elementos importantes tiene nuestro proyecto.

En una tienda de cuencos tibetanos podemos encontrar estos elementos:

  • Productos.
  • Categorías.
  • Usuarios administradores.
  • Mensajes de contacto.
  • Pedidos.
  • Detalles de pedido.
  • Carrito.
  • Imágenes.

Cada uno de estos elementos puede representarse con una clase.

Para empezar, trabajaremos con las clases principales:

  • Producto
  • Categoria
  • Usuario
  • Conexion

Después añadiremos las clases encargadas de acceder a la base de datos:

  • ProductoDAO
  • CategoriaDAO
  • UsuarioDAO

Fase 3: estructura de carpetas del proyecto

En esta versión necesitaremos una estructura más organizada.

Una posible estructura será esta:

sonido-interior-poo/

├── index.php
├── catalogo.php
├── login.php
├── validar-login.php
├── logout.php

├── admin/
│ ├── panel.php
│ ├── productos.php
│ ├── producto-alta.php
│ ├── producto-guardar.php
│ ├── producto-editar.php
│ ├── producto-actualizar.php
│ └── producto-borrar.php

├── includes/
│ ├── header.php
│ ├── menu.php
│ ├── menu-admin.php
│ ├── footer.php
│ └── seguridad.php

├── clases/
│ ├── Conexion.php
│ ├── Producto.php
│ ├── Categoria.php
│ ├── Usuario.php
│ ├── ProductoDAO.php
│ ├── CategoriaDAO.php
│ └── UsuarioDAO.php

├── css/
│ └── estilos.css

├── img/
│ └── productos/

└── sql/
└── tienda_cuencos.sql

La carpeta más importante de esta versión será clases/.

Ahí guardaremos las clases del proyecto.


Fase 4: creación de la clase Conexion

La primera clase que crearemos será Conexion.

Esta clase se encargará de conectarse a la base de datos.

En lugar de tener un archivo conexion.php con variables sueltas, crearemos una clase con un método que devuelva la conexión.

Ejemplo:

<?php

class Conexion {

private $servidor = "localhost";
private $usuario = "root";
private $password = "";
private $baseDatos = "tienda_cuencos";

public function conectar() {
$conexion = new mysqli(
$this->servidor,
$this->usuario,
$this->password,
$this->baseDatos
);

if ($conexion->connect_error) {
die("Error de conexión: " . $conexion->connect_error);
}

$conexion->set_charset("utf8");

return $conexion;
}
}

?>

Con esta clase podremos conectarnos a la base de datos desde cualquier DAO.

Ejemplo:

$conexionBD = new Conexion();
$conexion = $conexionBD->conectar();

Fase 5: creación de la clase Producto

Después crearemos la clase Producto.

Esta clase representará un producto de la tienda.

Tendrá atributos privados como:

  • ID.
  • Nombre.
  • Descripción.
  • Precio.
  • Stock.
  • Imagen.
  • Diámetro.
  • Peso.
  • Material.
  • Nota musical.
  • Procedencia.
  • Activo.
  • Categoría.

Ejemplo:

<?php

class Producto {

private $idProducto;
private $nombre;
private $descripcion;
private $precio;
private $stock;
private $imagen;
private $diametro;
private $peso;
private $material;
private $notaMusical;
private $procedencia;
private $activo;
private $idCategoria;

public function __construct(
$idProducto = null,
$nombre = "",
$descripcion = "",
$precio = 0,
$stock = 0,
$imagen = "",
$diametro = 0,
$peso = 0,
$material = "",
$notaMusical = "",
$procedencia = "",
$activo = true,
$idCategoria = null
) {
$this->idProducto = $idProducto;
$this->nombre = $nombre;
$this->descripcion = $descripcion;
$this->precio = $precio;
$this->stock = $stock;
$this->imagen = $imagen;
$this->diametro = $diametro;
$this->peso = $peso;
$this->material = $material;
$this->notaMusical = $notaMusical;
$this->procedencia = $procedencia;
$this->activo = $activo;
$this->idCategoria = $idCategoria;
}

public function getIdProducto() {
return $this->idProducto;
}

public function getNombre() {
return $this->nombre;
}

public function setNombre($nombre) {
$this->nombre = $nombre;
}

public function getDescripcion() {
return $this->descripcion;
}

public function setDescripcion($descripcion) {
$this->descripcion = $descripcion;
}

public function getPrecio() {
return $this->precio;
}

public function setPrecio($precio) {
$this->precio = $precio;
}

public function getStock() {
return $this->stock;
}

public function setStock($stock) {
$this->stock = $stock;
}
}

?>

Durante el proyecto iremos completando la clase con todos los getters y setters necesarios.


Fase 6: creación de la clase Categoria

La clase Categoria representará una categoría de producto.

Ejemplo:

<?php

class Categoria {

private $idCategoria;
private $nombre;
private $descripcion;
private $activo;

public function __construct(
$idCategoria = null,
$nombre = "",
$descripcion = "",
$activo = true
) {
$this->idCategoria = $idCategoria;
$this->nombre = $nombre;
$this->descripcion = $descripcion;
$this->activo = $activo;
}

public function getIdCategoria() {
return $this->idCategoria;
}

public function getNombre() {
return $this->nombre;
}

public function setNombre($nombre) {
$this->nombre = $nombre;
}

public function getDescripcion() {
return $this->descripcion;
}

public function setDescripcion($descripcion) {
$this->descripcion = $descripcion;
}

public function getActivo() {
return $this->activo;
}

public function setActivo($activo) {
$this->activo = $activo;
}
}

?>

Esta clase nos permitirá trabajar con categorías de forma ordenada.


Fase 7: creación de la clase Usuario

La clase Usuario representará a los usuarios administradores.

Tendrá atributos como:

  • ID.
  • Nombre.
  • Email.
  • Usuario.
  • Password.
  • Rol.

Ejemplo:

<?php

class Usuario {

private $idUsuario;
private $nombre;
private $email;
private $usuario;
private $password;
private $rol;

public function __construct(
$idUsuario = null,
$nombre = "",
$email = "",
$usuario = "",
$password = "",
$rol = "admin"
) {
$this->idUsuario = $idUsuario;
$this->nombre = $nombre;
$this->email = $email;
$this->usuario = $usuario;
$this->password = $password;
$this->rol = $rol;
}

public function getIdUsuario() {
return $this->idUsuario;
}

public function getNombre() {
return $this->nombre;
}

public function getUsuario() {
return $this->usuario;
}

public function getPassword() {
return $this->password;
}

public function getRol() {
return $this->rol;
}
}

?>

Esta clase se usará principalmente para el login y la gestión de sesiones.


Fase 8: qué es una clase DAO

En esta versión del proyecto usaremos clases DAO.

DAO significa Data Access Object, es decir, objeto de acceso a datos.

Una clase DAO se encarga de hablar con la base de datos.

Por ejemplo, la clase Producto representa los datos de un producto, pero no debería ser la encargada de hacer consultas SQL.

Para eso tendremos ProductoDAO.

La clase ProductoDAO tendrá métodos como:

  • obtenerTodos()
  • obtenerPorId($id)
  • crear($producto)
  • actualizar($producto)
  • eliminar($id)
  • disminuirStock($idProducto, $cantidad)

La ventaja es que las consultas SQL quedan agrupadas en una clase concreta.


Fase 9: creación de ProductoDAO

La clase ProductoDAO será una de las más importantes del proyecto.

Ejemplo inicial:

<?php

require_once "Conexion.php";
require_once "Producto.php";

class ProductoDAO {

private $conexion;

public function __construct() {
$conexionBD = new Conexion();
$this->conexion = $conexionBD->conectar();
}

public function obtenerTodos() {
$sql = "SELECT * FROM productos WHERE activo = 1";
$resultado = $this->conexion->query($sql);

$productos = [];

while ($fila = $resultado->fetch_assoc()) {
$producto = new Producto(
$fila["id_producto"],
$fila["nombre"],
$fila["descripcion"],
$fila["precio"],
$fila["stock"],
$fila["imagen"],
$fila["diametro"],
$fila["peso"],
$fila["material"],
$fila["nota_musical"],
$fila["procedencia"],
$fila["activo"],
$fila["id_categoria"]
);

$productos[] = $producto;
}

return $productos;
}
}

?>

Ahora, desde catalogo.php, podremos obtener los productos así:

require_once "clases/ProductoDAO.php";

$productoDAO = new ProductoDAO();
$productos = $productoDAO->obtenerTodos();

Y después recorrerlos:

foreach ($productos as $producto) {
echo "<h3>" . $producto->getNombre() . "</h3>";
echo "<p>" . $producto->getPrecio() . " €</p>";
}

Fase 10: mostrar el catálogo usando objetos

En la página catalogo.php, ya no haremos directamente una consulta SQL.

La página simplemente pedirá los productos a ProductoDAO.

Ejemplo:

<?php
require_once "clases/ProductoDAO.php";
include("includes/header.php");
include("includes/menu.php");

$productoDAO = new ProductoDAO();
$productos = $productoDAO->obtenerTodos();
?>

<main class="contenedor">
<h1>Catálogo de productos</h1>

<section class="grid-productos">
<?php foreach ($productos as $producto): ?>
<article class="tarjeta-producto">
<img src="img/productos/<?php echo $producto->getImagen(); ?>" alt="<?php echo $producto->getNombre(); ?>">
<h3><?php echo $producto->getNombre(); ?></h3>
<p><?php echo $producto->getPrecio(); ?> €</p>
</article>
<?php endforeach; ?>
</section>
</main>

<?php include("includes/footer.php"); ?>

Para que esto funcione, la clase Producto deberá tener métodos como:

  • getImagen()
  • getNombre()
  • getPrecio()

Fase 11: alta de productos usando objetos

Cuando enviemos el formulario de alta, recogeremos los datos del formulario y crearemos un objeto Producto.

Ejemplo en producto-guardar.php:

require_once "../clases/Producto.php";
require_once "../clases/ProductoDAO.php";

$producto = new Producto();

$producto->setNombre($_POST["nombre"]);
$producto->setDescripcion($_POST["descripcion"]);
$producto->setPrecio($_POST["precio"]);
$producto->setStock($_POST["stock"]);
$producto->setMaterial($_POST["material"]);
$producto->setProcedencia($_POST["procedencia"]);

$productoDAO = new ProductoDAO();
$productoDAO->crear($producto);

Después, dentro de ProductoDAO, tendremos el método crear().

Ejemplo:

public function crear($producto) {
$sql = "INSERT INTO productos
(nombre, descripcion, precio, stock, material, procedencia)
VALUES (?, ?, ?, ?, ?, ?)";

$stmt = $this->conexion->prepare($sql);

$nombre = $producto->getNombre();
$descripcion = $producto->getDescripcion();
$precio = $producto->getPrecio();
$stock = $producto->getStock();
$material = $producto->getMaterial();
$procedencia = $producto->getProcedencia();

$stmt->bind_param(
"ssdiss",
$nombre,
$descripcion,
$precio,
$stock,
$material,
$procedencia
);

return $stmt->execute();
}

Este enfoque es más limpio porque el archivo del formulario no contiene directamente toda la consulta SQL.


Fase 12: listado administrativo usando objetos

El listado administrativo también usará ProductoDAO.

La página admin/productos.php pedirá todos los productos:

$productoDAO = new ProductoDAO();
$productos = $productoDAO->obtenerTodos();

Después los mostrará en una tabla:

<?php foreach ($productos as $producto): ?>
<tr>
<td><?php echo $producto->getIdProducto(); ?></td>
<td><?php echo $producto->getNombre(); ?></td>
<td><?php echo $producto->getPrecio(); ?> €</td>
<td><?php echo $producto->getStock(); ?></td>
<td>
<a href="producto-editar.php?id=<?php echo $producto->getIdProducto(); ?>">Editar</a>
<a href="producto-borrar.php?id=<?php echo $producto->getIdProducto(); ?>">Borrar</a>
</td>
</tr>
<?php endforeach; ?>

La página se centra en mostrar información, no en saber cómo se consulta la base de datos.


Fase 13: obtener un producto por ID

Para editar un producto necesitaremos recuperarlo por su ID.

Añadiremos este método a ProductoDAO:

public function obtenerPorId($id) {
$sql = "SELECT * FROM productos WHERE id_producto = ?";
$stmt = $this->conexion->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();

$resultado = $stmt->get_result();

if ($fila = $resultado->fetch_assoc()) {
return new Producto(
$fila["id_producto"],
$fila["nombre"],
$fila["descripcion"],
$fila["precio"],
$fila["stock"],
$fila["imagen"],
$fila["diametro"],
$fila["peso"],
$fila["material"],
$fila["nota_musical"],
$fila["procedencia"],
$fila["activo"],
$fila["id_categoria"]
);
}

return null;
}

Después, desde producto-editar.php:

$id = $_GET["id"];

$productoDAO = new ProductoDAO();
$producto = $productoDAO->obtenerPorId($id);

Así podremos rellenar el formulario de edición con los datos actuales del producto.


Fase 14: actualización de productos

Cuando se envíe el formulario de edición, crearemos un objeto Producto con los datos actualizados.

Después llamaremos al método actualizar() de ProductoDAO.

Ejemplo:

$producto = new Producto();

$producto->setIdProducto($_POST["id_producto"]);
$producto->setNombre($_POST["nombre"]);
$producto->setDescripcion($_POST["descripcion"]);
$producto->setPrecio($_POST["precio"]);
$producto->setStock($_POST["stock"]);

$productoDAO = new ProductoDAO();
$productoDAO->actualizar($producto);

El método actualizar() hará una consulta UPDATE.

public function actualizar($producto) {
$sql = "UPDATE productos
SET nombre = ?, descripcion = ?, precio = ?, stock = ?
WHERE id_producto = ?";

$stmt = $this->conexion->prepare($sql);

$nombre = $producto->getNombre();
$descripcion = $producto->getDescripcion();
$precio = $producto->getPrecio();
$stock = $producto->getStock();
$id = $producto->getIdProducto();

$stmt->bind_param("ssdii", $nombre, $descripcion, $precio, $stock, $id);

return $stmt->execute();
}

Fase 15: borrado lógico de productos

En lugar de borrar físicamente un producto, lo marcaremos como inactivo.

En ProductoDAO añadiremos un método eliminar():

public function eliminar($id) {
$sql = "UPDATE productos SET activo = 0 WHERE id_producto = ?";
$stmt = $this->conexion->prepare($sql);
$stmt->bind_param("i", $id);

return $stmt->execute();
}

Desde producto-borrar.php:

$id = $_GET["id"];

$productoDAO = new ProductoDAO();
$productoDAO->eliminar($id);

header("Location: productos.php");
exit();

Con esto el producto seguirá en la base de datos, pero dejará de mostrarse en la tienda.


Fase 16: gestión de categorías con CategoriaDAO

Igual que tenemos ProductoDAO, crearemos CategoriaDAO.

Esta clase tendrá métodos como:

  • obtenerTodas()
  • obtenerPorId($id)
  • crear($categoria)
  • actualizar($categoria)
  • eliminar($id)

Ejemplo básico:

<?php

require_once "Conexion.php";
require_once "Categoria.php";

class CategoriaDAO {

private $conexion;

public function __construct() {
$conexionBD = new Conexion();
$this->conexion = $conexionBD->conectar();
}

public function obtenerTodas() {
$sql = "SELECT * FROM categorias WHERE activo = 1";
$resultado = $this->conexion->query($sql);

$categorias = [];

while ($fila = $resultado->fetch_assoc()) {
$categoria = new Categoria(
$fila["id_categoria"],
$fila["nombre"],
$fila["descripcion"],
$fila["activo"]
);

$categorias[] = $categoria;
}

return $categorias;
}
}

?>

Esto nos permitirá cargar categorías dinámicamente en el formulario de alta de productos.


Fase 17: login usando UsuarioDAO

Para el login crearemos la clase UsuarioDAO.

Esta clase tendrá un método para buscar un usuario por su nombre de usuario o email.

Ejemplo:

public function obtenerPorUsuario($usuario) {
$sql = "SELECT * FROM usuarios WHERE usuario = ? OR email = ?";
$stmt = $this->conexion->prepare($sql);
$stmt->bind_param("ss", $usuario, $usuario);
$stmt->execute();

$resultado = $stmt->get_result();

if ($fila = $resultado->fetch_assoc()) {
return new Usuario(
$fila["id_usuario"],
$fila["nombre"],
$fila["email"],
$fila["usuario"],
$fila["password"],
$fila["rol"]
);
}

return null;
}

Después, en validar-login.php:

session_start();

require_once "clases/UsuarioDAO.php";

$usuarioFormulario = $_POST["usuario"];
$passwordFormulario = $_POST["password"];

$usuarioDAO = new UsuarioDAO();
$usuario = $usuarioDAO->obtenerPorUsuario($usuarioFormulario);

if ($usuario != null && password_verify($passwordFormulario, $usuario->getPassword())) {
$_SESSION["usuario"] = $usuario->getUsuario();
$_SESSION["rol"] = $usuario->getRol();

header("Location: admin/panel.php");
exit();
} else {
header("Location: login.php?error=1");
exit();
}

Así el login queda mucho más ordenado.


Fase 18: protección de páginas privadas

Seguiremos usando sesiones para proteger la parte administrativa.

El archivo includes/seguridad.php puede mantenerse parecido a la versión anterior.

<?php

session_start();

if (!isset($_SESSION["usuario"])) {
header("Location: ../login.php");
exit();
}

?>

En cada página privada incluiremos este archivo.

<?php include("../includes/seguridad.php"); ?>

Fase 19: subida de imágenes usando una función auxiliar

La subida de imágenes puede hacerse al principio dentro de producto-guardar.php, pero podemos mejorarla creando una función o una clase auxiliar.

Una opción sencilla sería crear:

clases/GestorImagenes.php

Ejemplo:

<?php

class GestorImagenes {

public static function subirImagen($archivo, $carpetaDestino) {
$nombreImagen = time() . "_" . basename($archivo["name"]);
$rutaDestino = $carpetaDestino . $nombreImagen;

if (move_uploaded_file($archivo["tmp_name"], $rutaDestino)) {
return $nombreImagen;
}

return null;
}
}

?>

Después, en producto-guardar.php:

require_once "../clases/GestorImagenes.php";

$imagen = GestorImagenes::subirImagen(
$_FILES["imagen"],
"../img/productos/"
);

$producto->setImagen($imagen);

Esto permite separar la lógica de subida de archivos del resto del código.


Fase 20: autoload básico de clases

Cuando el proyecto crece, puede resultar pesado escribir muchos require_once.

Por ejemplo:

require_once "clases/Conexion.php";
require_once "clases/Producto.php";
require_once "clases/ProductoDAO.php";
require_once "clases/Categoria.php";
require_once "clases/CategoriaDAO.php";

Para evitarlo, podemos crear un pequeño autoload.

Ejemplo en includes/autoload.php:

<?php

spl_autoload_register(function ($nombreClase) {
require_once __DIR__ . "/../clases/" . $nombreClase . ".php";
});

?>

Después, en cada página solo haríamos:

require_once "includes/autoload.php";

O desde la carpeta admin:

require_once "../includes/autoload.php";

Esto nos permitirá cargar clases automáticamente cuando las necesitemos.


Fase 21: diagrama de clases

En esta versión será especialmente importante trabajar con un diagrama de clases.

El diagrama nos ayudará a entender qué clases existen y cómo se relacionan.

Algunas clases principales serán:

  • Conexion
  • Producto
  • Categoria
  • Usuario
  • ProductoDAO
  • CategoriaDAO
  • UsuarioDAO
  • GestorImagenes

La relación sería sencilla:

  • ProductoDAO usa Conexion.
  • ProductoDAO crea y devuelve objetos Producto.
  • CategoriaDAO usa Conexion.
  • CategoriaDAO crea y devuelve objetos Categoria.
  • UsuarioDAO usa Conexion.
  • UsuarioDAO crea y devuelve objetos Usuario.
  • Las páginas PHP usan los DAO para obtener o guardar datos.

Este diagrama nos permitirá ver que las páginas ya no hablan directamente con la base de datos, sino que lo hacen a través de clases especializadas.


Fase 22: diagrama de base de datos

El diagrama de base de datos seguirá siendo parecido al de la versión anterior.

Tendremos tablas como:

  • usuarios
  • categorias
  • productos
  • mensajes
  • pedidos
  • detalle_pedido
  • carrito
  • carrito_producto

La tabla más importante al principio será productos, relacionada con categorias.

La diferencia es que ahora intentaremos que las tablas tengan una correspondencia clara con las clases del proyecto.

Por ejemplo:

  • Tabla productos → Clase Producto
  • Tabla categorias → Clase Categoria
  • Tabla usuarios → Clase Usuario

Esto ayuda a entender la relación entre el modelo de datos y el modelo de objetos.


Fase 23: preparación para hosting

El despliegue en hosting será similar al de la versión anterior.

Tendremos que subir:

  • Archivos PHP.
  • Carpeta clases/.
  • Carpeta includes/.
  • Carpeta css/.
  • Carpeta img/.
  • Base de datos exportada.

También deberemos modificar los datos de conexión dentro de la clase Conexion.

En local podríamos tener:

private $servidor = "localhost";
private $usuario = "root";
private $password = "";
private $baseDatos = "tienda_cuencos";

En hosting tendremos otros datos:

private $servidor = "servidor_del_hosting";
private $usuario = "usuario_hosting";
private $password = "password_hosting";
private $baseDatos = "base_datos_hosting";

Esta parte nos ayudará a entender que una aplicación debe adaptarse al entorno donde se ejecuta.


Fase 24: ejecución en Docker

También podremos preparar el proyecto para ejecutarlo en Docker.

La diferencia principal será que la clase Conexion deberá conectarse al servicio de base de datos usando el nombre del contenedor.

Por ejemplo, si en docker-compose.yml el servicio de base de datos se llama db, entonces en PHP usaremos:

private $servidor = "db";

Ejemplo de docker-compose.yml:

services:
web:
image: php:8.2-apache
ports:
- "8080:80"
volumes:
- ./src:/var/www/html

db:
image: mariadb:10.11
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: tienda_cuencos
MYSQL_USER: usuario
MYSQL_PASSWORD: usuario

phpmyadmin:
image: phpmyadmin
ports:
- "8081:80"
environment:
PMA_HOST: db

Con esta configuración podríamos acceder a la web desde:

http://localhost:8080

Y a phpMyAdmin desde:

http://localhost:8081

Fase 25: documentación final del proyecto

Al terminar esta versión, cada alumno deberá documentar el proyecto.

La documentación debería incluir:

  • Explicación general del proyecto.
  • Diferencias entre la versión básica y la versión POO.
  • Estructura de carpetas.
  • Diagrama de clases.
  • Diagrama de base de datos.
  • Explicación de las clases principales.
  • Explicación de las clases DAO.
  • Explicación del CRUD.
  • Capturas de pantalla.
  • Problemas encontrados.
  • Mejoras posibles.
  • Conclusión personal.

En esta versión será especialmente importante que el alumno sepa explicar por qué se han creado ciertas clases y qué responsabilidad tiene cada una.


26. Qué aprenderemos con esta versión

Con esta versión aprenderemos a organizar mejor un proyecto PHP.

No solo veremos cómo hacer que una aplicación funcione, sino cómo estructurarla para que sea más clara y mantenible.

Aprenderemos conceptos como:

  • Clases.
  • Objetos.
  • Atributos.
  • Métodos.
  • Constructores.
  • Getters y setters.
  • Encapsulación.
  • DAO.
  • Separación de responsabilidades.
  • Consultas preparadas.
  • Sesiones.
  • Subida de archivos.
  • Reutilización de código.
  • Relación entre tablas y clases.
  • Preparación del proyecto para hosting o Docker.

Esta versión es un paso intermedio muy importante antes de trabajar con arquitecturas más avanzadas como MVC o frameworks como Laravel.


27. Conclusión

La versión orientada a objetos de Sonido Interior nos permitirá mejorar el mismo proyecto que ya conocemos.

En lugar de empezar una aplicación totalmente nueva, evolucionaremos una tienda sencilla construida con PHP básico hacia una estructura más ordenada y profesional.

Esto nos permitirá entender mejor por qué existe la programación orientada a objetos y qué problemas resuelve.

La idea principal es que el alumno vea una evolución clara:

Primero hacemos que funcione.

Después hacemos que esté mejor organizado.

Y finalmente preparamos el proyecto para poder crecer, mantenerse y desplegarse en un entorno real.