Puedo hacerte una sugerencia respecto a tu implementación? En vez de guardar todos los datos en arrays distintos... das demasiado espacio al error, si los indices de repente no se correspondieran los unos con los otros.
Podrías utilizar este acercamiento a tus objetos, donde dejás reservado a cada uno las competencias que le son necesarias. Fijate como todo el tema de trabajo con datos de un producto quedaron dentro de la clase Producto, mientras que los manejos de distintos productos, quedan dentro del Carrito.
Código PHP:
<?php
class Carrito
{
protected $productos = array();
/**
* Agrega un producto al carrito, ya sea asignando un array
* o un objeto del tipo Producto ya cargado
*/
public function agregaProducto($info)
{
if(is_array($info)) {
$product = new Producto();
$product->cargaPorArray($info);
} else if($info instanceof Producto) {
$product = $info;
} else {
throw Exception('Informacion de Producto invalida');
}
$id = $product->getId();
/** Si el producto ya existe, incrementa la cantidad */
if($this->existeProducto($id))
{
$this->obtieneProducto($id)->incrementaCant( $product->getCantidad() );
} else {
$this->productos[$id] = $product;
}
}
/**
* Retorna una bandera si el producto ya existe
*/
public function existeProducto($id)
{
return !empty($this->productos[$id]);
}
/**
* Accessor para un producto específico
* Devuelve null si no existe.
*/
public function obtieneProducto($id)
{
if ($this->existeProducto($id)) {
return $this->productos[$id];
} else {
return null;
}
}
/**
* Si pasamos un Producto como parámentro, usa el id para
* eliminarlo del carrito
*/
public function eliminaProducto($producto)
{
if ($producto instanceof Producto) {
$id = $producto->getId();
} else {
$id = (int) $producto;
}
unset($this->productos[$id]);
}
/**
* Calcula el precio total del carrito, tomando en cuenta
* el precio de cada producto y la cantidad de elementos
*/
public function getPrecio()
{
$total = 0;
foreach($this->productos as $producto) {
$total += $producto->getPrecio();
}
return round($total, 2);
}
/**
* Utiliza un objeto Renderer para representar el Carrito
* a través de tags HTML
*/
public function imprimeProductos()
{
/* Acá yo no soy muy amigo de mezclar HTML dentro de
objetos de negocio, asi que voy por una opcion
que suelo utilizar: Objetos Renderers */
return CarritoRenderer::render($this);
}
public function obtenerProductos()
{
return $this->productos;
}
}
class Producto
{
protected $id = null;
protected $nombre = '';
protected $descripcion = '';
protected $tamano = null;
protected $precio = null;
protected $cantidad = null;
/**
* Accessors, se puede arreglar con un método __get
*/
public function getId()
{
return $this->id;
}
public function getNombre()
{
return $this->nombre;
}
public function getDescripcion()
{
return $this->descripcion;
}
public function getTamano()
{
return $this->tamano;
}
public function getCantidad()
{
return $this->cantidad;
}
public function incrementaCant( $cantidad = 1 )
{
return $this->cantidad+=$cantidad;
}
public function getPrecio()
{
return $this->precio * $this->cantidad;
}
public function cargaPorArray($data)
{
if(!$this->id) { // evitamos sobreescribir un producto
$this->id = $data['id'];
$this->nombre = $data['nombre'];
$this->descripcion = $data['descripcion'];
$this->tamano = $data['tamano'];
$this->precio = $data['precio'];
$this->cantidad = $data['cantidad'];
} else {
throw new Exception('Intentaste cargar un producto que ya estaba cargado.');
}
}
}
Es una opinión personal, pero a mi no me gusta mezclar tags de HTML junto a mi lógica de negocios, así puedo mantener la funcionalidad independiente de la presentación. En este caso y para este fin, es que creé dos objetos nuevos, que se encargar de representar en HTML a los Productos y al Carrito. Se llaman Renderers porque se me ocurrió llamarlos asi, pero te separan la lógica de presentación por si después querés cambiar el HTML por GTK o por linea de comandos:
Código PHP:
class CarritoRenderer
{
static public function render(Carrito $carrito)
{
$html = '';
foreach($carrito->obtenerProductos() as $producto)
{
$html.= ProductoRenderer::render($producto);
}
return $html;
}
}
class ProductoRenderer
{
static public function render(Producto $producto)
{
$html = '';
$id = $producto->getId();
if ($id) {
$html.= "<li id='{$id}'>";
$html.= self::renderImagen($producto);
$html.= self::renderNombre($producto);
$html.= self::renderDescripcion($producto);
$html.= self::renderCantidad($producto);
$html.= self::renderPrecio($producto);
$html.= "<a class='delete' href='eliminar_producto_carrito.php?id={$id}'>Eliminar</a>";
$html.= '</li>';
}
return $html;
}
static function renderImagen(Producto $producto)
{
return "<a href='products.php?id={$producto->getId()}&tamano={$producto->getTamano()}'
class='item' >
<img src='/images/canastillas/{$producto->getId()}{$producto->getTamano()}.jpg' />
</a>";
}
static function renderNombre(Producto $producto)
{
return "<h4>{$producto->getNombre()}</h4>";
}
static function renderDescripcion(Producto $producto)
{
return "<p>{$producto->getDescripcion()}</p>";
}
static function renderCantidad(Producto $producto)
{
return "<h5>Unidades: {$producto->getCantidad()}</h5>";
}
static function renderPrecio(Producto $producto)
{
return "<h3>{$producto->getPrecio()}</h3>";
}
}
Si te fijás, estos objetos están preparados para ser dibujados con el método render() o llamando a los renders más específicos para armar la estructura que vos quieras luego en tus vistas.
Si vamos a la utlización de estas nuevas clases, podrías hacerlo asi :
Código PHP:
<?php
require_once ("carrito.php");
require_once ("operacionesbd.php");
require_once ('crearconexionbd.php');
crearConexionBD();
session_start();
$id = $_GET["id"]?$_GET["id"]:null;
$tipo = $_GET["type"]?$_GET["type"]:null;
$carrito = new Carrito();
if ($id && $tipo) {
// Pedis los datos.
$elemento = mysql_fetch_assoc(seleccionaCanastillaProductoPorID($id, $tipo));
$elemento['descripcion'] = substr($elemento["descripcion"], 0, 70) . "...";
//Dependiendo del tipo ...
switch($tipo) {
case 'mini':
case 'midi':
case 'maxi':
$precio = $elemento[$tipo];
break;
default:
$precio = $elemento['precio'];
break;
}
/* Acá asegurate que los indices del array son los mismos
que los índices que necesita el objeto Producto */
$carrito->agregaProducto($elemento);
echo $c->imprimeProductos();
}
Ahora, cómo hacemos para guardar el carrito de compra entero y persistirlo en la sesión?
Esta parte no es TAN simple. Hay algunos métodos, te voy a contar cuál es el que a mi me parece un poco más complicado pero menos inestable. Siempre es bueno separar competencias, asi que la encargada de guardar el carrito en una sesión, debería ser otra clase. Veamos como la podemos hacer lo más simple posible...
Código PHP:
class CarritoSessionMapper
{
static public function getCarrito()
{
if(!session_id()) {
session_start();
}
if (!empty($_SESSION['carrito']) && is_string($_SESSION['carrito']) )
{
return unserialize($_SESSION['carrito']);
} else {
return null;
}
}
static public function setCarrito(Carrito $carrito)
{
if(!session_id()) {
session_start();
}
$_SESSION['carrito'] = serialize($carrito);
}
}
Y la manera de utilizarlo, sería algo asi :
Código PHP:
if( ($c = CarritoSessionMapper::getCarrito()) === null) {
$c = new Carrito();
}
$c->agregaProducto(array(
'id' => 5,
'nombre' => 'Zend Framework',
'descripcion' => 'Excelente Framework de Desarrollo',
'tamano' => 'maxi',
'precio' => 25,
'cantidad' => '3',
));
CarritoSessionMapper::setCarrito($c);
var_dump($_SESSION);
Ya saben, este tipo de ejercicios me encantan. Si alguna parte no se entendió o no funcionó, me avisan y lo revisamos.
Saludos !