Hoy vamos a ver cómo implementar un carrito de compras con PHP y MySQL. Vamos a poner una especie de tienda o e-commerce con PHP en donde el usuario puede agregar productos al carrito, ver su carrito de compras con el total, y quitar productos del mismo.
Para ello vamos a usar la sesión y MySQL. Con la sesión vamos a identificar al usuario y la gestión del carrito se hará a través de esta base de datos. Ten en cuenta que aquí el punto es el carrito de compras, no una tienda online.
La única desventaja que tendremos es que si el usuario abandona el carrito de compras los datos no serán eliminados, pero esto se puede arreglar implementando todo el carrito como un array en la sesión tal como lo hice en mi sistema de ventas con PHP.
Te dejaré el código completo al final del post, como siempre, gratuito y open source.
Como lo dije, esto será más que nada un ejemplo. Tendremos el módulo de gestión de productos para agregar y eliminar productos como administradores de la tienda.
Por otro lado existirá el módulo de tienda en donde el usuario podrá agregar productos al carro y más tarde ver los productos que tiene en su cesta así como el total para terminar su compra.
Una vez que el usuario termine su compra, el programador es libre de hacer lo que guste con los productos. Es decir, puede registrar la venta, restar existencia, etcétera.
Antes de comenzar, te mostraré cómo configurar la base de datos. Las tablas son las siguientes:
CREATE TABLE IF NOT EXISTS productos(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(255) NOT NULL,
descripcion VARCHAR(1024) NOT NULL,
precio DECIMAL(9,2)
);
CREATE TABLE IF NOT EXISTS carrito_usuarios(
id_sesion VARCHAR(255) NOT NULL,
id_producto BIGINT UNSIGNED NOT NULL,
FOREIGN KEY (id_producto) REFERENCES productos(id)
ON UPDATE CASCADE ON DELETE CASCADE
);
Tenemos la tabla de los productos de la tienda y la tabla para la relación entre los productos que el usuario agrega a su cesta. Para identificar al usuario usamos su id de sesión.
Recuerda que debes crear el archivo env.php
basándote en el archivo env.ejemplo.php
para configurar las credenciales de la base de datos. El mío se ve así:
; <?php exit; ?>
MYSQL_DATABASE_NAME = "tienda"
MYSQL_USER = "root"
MYSQL_PASSWORD = ""
Para obtener la conexión tenemos la siguiente función:
<?php
function obtenerVariableDelEntorno($key)
{
if (defined("_ENV_CACHE")) {
$vars = _ENV_CACHE;
} else {
$file = "env.php";
if (!file_exists($file)) {
throw new Exception("El archivo de las variables de entorno ($file) no existe. Favor de crearlo");
}
$vars = parse_ini_file($file);
define("_ENV_CACHE", $vars);
}
if (isset($vars[$key])) {
return $vars[$key];
} else {
throw new Exception("La clave especificada (" . $key . ") no existe en el archivo de las variables de entorno");
}
}
function obtenerConexion()
{
$password = obtenerVariableDelEntorno("MYSQL_PASSWORD");
$user = obtenerVariableDelEntorno("MYSQL_USER");
$dbName = obtenerVariableDelEntorno("MYSQL_DATABASE_NAME");
$database = new PDO('mysql:host=localhost;dbname=' . $dbName, $user, $password);
$database->query("set names utf8;");
$database->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
$database->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$database->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
return $database;
}
Comencemos viendo la administración de productos. Las funciones que se encargan de todo ello son las siguientes:
<?php
function eliminarProducto($id)
{
$bd = obtenerConexion();
$sentencia = $bd->prepare("DELETE FROM productos WHERE id = ?");
return $sentencia->execute([$id]);
}
function guardarProducto($nombre, $precio, $descripcion)
{
$bd = obtenerConexion();
$sentencia = $bd->prepare("INSERT INTO productos(nombre, precio, descripcion) VALUES(?, ?, ?)");
return $sentencia->execute([$nombre, $precio, $descripcion]);
}
Todo esto está dentro del archivo de funciones globales. Más tarde vamos a incluir este archivo e invocarlas. Por ejemplo, tenemos el formulario:
<?php include_once "encabezado.php" ?>
<div class="columns">
<div class="column is-one-third">
<h2 class="is-size-2">Nuevo producto</h2>
<form action="guardar_producto.php" method="post">
<div class="field">
<label for="nombre">Nombre</label>
<div class="control">
<input required id="nombre" class="input" type="text" placeholder="Nombre" name="nombre">
</div>
</div>
<div class="field">
<label for="descripcion">Descripción</label>
<div class="control">
<textarea name="descripcion" class="textarea" id="descripcion" cols="30" rows="5" placeholder="Descripción" required></textarea>
</div>
</div>
<div class="field">
<label for="precio">Precio</label>
<div class="control">
<input required id="precio" name="precio" class="input" type="number" placeholder="Precio">
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-success">Guardar</button>
<a href="productos.php" class="button is-warning">Volver</a>
</div>
</div>
</form>
</div>
</div>
<?php include_once "pie.php" ?>
El mismo será enviado a guardar_producto.php
en donde recibimos los datos, incluimos las funciones, invocamos al método necesario y redireccionamos. Así de simple. Este patrón se sigue en los demás archivos.
<?php
if (!isset($_POST["nombre"]) || !isset($_POST["precio"]) || !isset($_POST["descripcion"])) {
exit("Faltan datos");
}
include_once "funciones.php";
guardarProducto($_POST["nombre"], $_POST["precio"], $_POST["descripcion"]);
header("Location: productos.php");
Como lo dije, no es una tienda completa. Es más bien un listado de todos los productos, destinada más al usuario que agregará los productos a su carro. En este caso cada producto tiene un botón para agregar al carrito.
También se está verificando si el producto ya está en el carrito de compras, en ese caso se muestra un botón para quitarlo.
El código para mostrar esto es el siguiente:
<?php include_once "encabezado.php" ?>
<?php
include_once "funciones.php";
$productos = obtenerProductos();
?>
<div class="columns">
<div class="column">
<h2 class="is-size-2">Tienda</h2>
</div>
</div>
<?php foreach ($productos as $producto) { ?>
<div class="columns">
<div class="column is-full">
<div class="card">
<header class="card-header">
<p class="card-header-title is-size-4">
<?php echo $producto->nombre ?>
</p>
</header>
<div class="card-content">
<div class="content">
<?php echo $producto->descripcion ?>
</div>
<?php if (productoYaEstaEnCarrito($producto->id)) { ?>
<h1 class="is-size-3">$<?php echo number_format($producto->precio, 2) ?></h1>
<form action="eliminar_del_carrito.php" method="post">
<input type="hidden" name="id_producto" value="<?php echo $producto->id ?>">
<span class="button is-success">
<i class="fa fa-check"></i> En el carrito
</span>
<button class="button is-danger">
<i class="fa fa-trash-o"></i> Quitar
</button>
</form>
<?php } else { ?>
<form action="agregar_al_carrito.php" method="post">
<input type="hidden" name="id_producto" value="<?php echo $producto->id ?>">
<button class="button is-primary">
<i class="fa fa-cart-plus"></i> Agregar al carrito
</button>
</form>
<?php } ?>
</div>
</div>
</div>
</div>
<?php } ?>
<?php include_once "pie.php" ?>
Estamos combinando código de PHP con HTML para mostrar el valor. En este caso usamos las funciones del archivo que mencioné anteriormente y que te dejaré al final del post dentro del código completo.
Estas funciones de las que hablo nos permiten saber si el producto ya está en el carrito, obtener todos los productos, etcétera.
Para agregar un producto al carrito en este ejemplo con PHP se muestra un botón dentro de un formulario que lleva el id del producto:
<form action="agregar_al_carrito.php" method="post">
<input type="hidden" name="id_producto" value="<?php echo $producto->id ?>">
<button class="button is-primary">
<i class="fa fa-cart-plus"></i> Agregar al carrito
</button>
</form>
El action
del formulario es agregar_al_carrito.php
, ahí recibimos el ID e invocamos a la función pertinente:
<?php
include_once "funciones.php";
if (!isset($_POST["id_producto"])) {
exit("No hay id_producto");
}
agregarProductoAlCarrito($_POST["id_producto"]);
header("Location: tienda.php");
La verdadera magia está ocurriendo en las funciones:
<?php
function agregarProductoAlCarrito($idProducto)
{
// Ligar el id del producto con el usuario a través de la sesión
$bd = obtenerConexion();
iniciarSesionSiNoEstaIniciada();
$idSesion = session_id();
$sentencia = $bd->prepare("INSERT INTO carrito_usuarios(id_sesion, id_producto) VALUES (?, ?)");
return $sentencia->execute([$idSesion, $idProducto]);
}
Lo que hacemos es obtener la conexión, iniciar la sesión si no está iniciada, obtener el id de sesión de ese usuario e insertar un registro que solo llevará el id de la sesión y el id del producto. Todo eso con sentencias preparadas para evitar inyecciones SQL.
Ya después podemos obtener los productos igualmente a través del id de sesión:
<?php
function obtenerIdsDeProductosEnCarrito()
{
$bd = obtenerConexion();
iniciarSesionSiNoEstaIniciada();
$sentencia = $bd->prepare("SELECT id_producto FROM carrito_usuarios WHERE id_sesion = ?");
$idSesion = session_id();
$sentencia->execute([$idSesion]);
return $sentencia->fetchAll(PDO::FETCH_COLUMN);
}
O también obtener, a través de un INNER JOIN
, los detalles de los productos del carrito de compras:
<?php
function obtenerProductosEnCarrito()
{
$bd = obtenerConexion();
iniciarSesionSiNoEstaIniciada();
$sentencia = $bd->prepare("SELECT productos.id, productos.nombre, productos.descripcion, productos.precio
FROM productos
INNER JOIN carrito_usuarios
ON productos.id = carrito_usuarios.id_producto
WHERE carrito_usuarios.id_sesion = ?");
$idSesion = session_id();
$sentencia->execute([$idSesion]);
return $sentencia->fetchAll();
}
Y para quitar un producto del carrito, eliminamos usando el id del producto y el de sesión:
<?php
function quitarProductoDelCarrito($idProducto)
{
$bd = obtenerConexion();
iniciarSesionSiNoEstaIniciada();
$idSesion = session_id();
$sentencia = $bd->prepare("DELETE FROM carrito_usuarios WHERE id_sesion = ? AND id_producto = ?");
return $sentencia->execute([$idSesion, $idProducto]);
}
Te repito, en este caso si el usuario abandona el carrito de compras (es decir, cierra la página y luego la sesión cuenta con otro id) los datos se quedarán ahí por siempre.
Una solución podría ser guardar los productos en la sesión, o guardar la fecha del registro de la última actividad del usuario para eliminar los registros de la base de datos periódicamente.
Ya para terminar veamos qué pasa cuando el usuario ve su cesta de compras. Simplemente obtenemos todos los productos que ha agregado y los mostramos en una tabla con un botón para quitarlos.
También mostramos el total a pagar y un botón para terminar la compra. El código es:
<?php include_once "encabezado.php" ?>
<?php
include_once "funciones.php";
$productos = obtenerProductosEnCarrito();
if (count($productos) <= 0) {
?>
<section class="hero is-info">
<div class="hero-body">
<div class="container">
<h1 class="title">
Todavía no hay productos
</h1>
<h2 class="subtitle">
Visita la tienda para agregar productos a tu carrito
</h2>
<a href="tienda.php" class="button is-warning">Ver tienda</a>
</div>
</div>
</section>
<?php } else { ?>
<div class="columns">
<div class="column">
<h2 class="is-size-2">Mi carrito de compras</h2>
<table class="table">
<thead>
<tr>
<th>Nombre</th>
<th>Descripción</th>
<th>Precio</th>
<th>Quitar</th>
</tr>
</thead>
<tbody>
<?php
$total = 0;
foreach ($productos as $producto) {
$total += $producto->precio;
?>
<tr>
<td><?php echo $producto->nombre ?></td>
<td><?php echo $producto->descripcion ?></td>
<td>$<?php echo number_format($producto->precio, 2) ?></td>
<td>
<form action="eliminar_del_carrito.php" method="post">
<input type="hidden" name="id_producto" value="<?php echo $producto->id ?>">
<input type="hidden" name="redireccionar_carrito">
<button class="button is-danger">
<i class="fa fa-trash-o"></i>
</button>
</form>
</td>
<?php } ?>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2" class="is-size-4 has-text-right"><strong>Total</strong></td>
<td colspan="2" class="is-size-4">
$<?php echo number_format($total, 2) ?>
</td>
</tr>
</tfoot>
</table>
<a href="terminar_compra.php" class="button is-success is-large"><i class="fa fa-check"></i> Terminar compra</a>
</div>
</div>
<?php } ?>
<?php include_once "pie.php" ?>
Por cierto, si el carrito está vacío se muestra otra cosa:
En el menú de navegación también se muestra la cantidad de elementos que hay en el carrito:
<strong>Ver carrito <?php
include_once "funciones.php";
$conteo = count(obtenerIdsDeProductosEnCarrito());
if ($conteo > 0) {
printf("(%d)", $conteo);
}
?> <i class="fa fa-shopping-cart"></i></strong>
El conteo solo se muestra si el mismo es mayor que 0. Cuando el usuario termina la compra, solo se hace un var_dump
de los productos; ahí el programador es libre de adaptar el proyecto a sus necesidades.
No he mostrado todo el código, solo el más relevante e importante que hacía falta explicar, pues si mostrara todo, el post sería demasiado extenso.
Te dejo el código completo en mi GitHub. También te recomiendo más posts sobre PHP y te invito a ver otros proyectos que he realizado.
Igualmente he implementado un carrito de compras usando JavaScript puro, puedes ver el ejemplo aquí.
En este post te enseñaré a imprimir la letra ñ en una impresora térmica. Voy…
En este post te quiero compartir mi experiencia tramitando un acta de nacimiento de México…
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Esta web usa cookies.
Ver comentarios
Mil gracias por este tutorial :'D