In questo articolo vedremo come implementare un carrello di un e-commerce con PHP.

Il carrello può essere implementato usando la sessione corrente dove verrà serializzato un oggetto contenente l'elenco dei prodotti con relativo prezzo, totale parziale e quantità e il totale complessivo del carrello.

L'oggetto non sarà altro che la rappresentazione di una classe Cart che avrà metodi e proprietà con cui operare sui prodotti da aggiungere, rimuovere e aggiornare.

La classe Cart avrà queste proprietà:

Nome Visibilità Descrizione
items private Array contenente i prodotti del carrello.
total private Totale del carrello.

E questi metodi:

Nome Visibilità Parametri Descrizione
emptyCart() public Nessuno Resetta le proprietà items e total.
setItems() public $items Imposta la proprietà items sull'array specificato.
getCart() public $serialize Restituisce un array associativo con l'elenco dei prodotti e il totale. Se $serialize è true, l'array viene serializzato.
getItems() public Nessuno Restituisce l'elenco dei prodotti.
setTotal() public $value Imposta la proprietà total sul valore specificato.
getTotal() public Nessuno Restituisce il totale del carrello.
updateCart() public $product, $quantity Aggiorna un prodotto del carrello con una nuova quantità.
removeFromCart() public $product Rimuove un prodotto dal carrello.
addToCart() public $product, $quantity Aggiunge un prodotto al carrello.
isInCart() private $product Verifica se un prodotto è già nel carrello.
calculateTotal() private Nessuno Calcola il totale del carrello.
hasItems() public Nessuno Verifica che nel carrello siano presenti dei prodotti.

Il codice completo della classe è il seguente:

class Cart {
    private $items;
    private $total;

    public function __construct() {
        $this->items = [];
        $this->total = 0.00;
    }

    public function emptyCart() {
        $this->items = [];
        $this->total = 0.00;
    }

    public function setItems($items) {
        $this->items = $items;
    }

    public function getCart($serialize = false) {
        $cart = [
          'items' => $this->getItems(),
          'total' => $this->getTotal()
        ];

        if($serialize) {
            return serialize($cart);
        }

        return $cart;
    }

    public function getItems() {
        $items = [];

        if($this->hasItems()) {
            foreach($this->items as $item) {
                $items[] = [
                    'id' => $item['id'],
                    'name' => $item['name'],
                    'description' => $item['description'],
                    'price' => $item['price'],
                    'quantity' => $item['quantity'],
                    'subtotal' => $item['subtotal']
                ];
            }
        }

        return $items;
    }

    public function setTotal($value) {
        $this->total = $value;
    }

    public function getTotal() {
        return $this->total;
    }

    public function updateCart($product, $quantity) {
        if($this->hasItems()) {
            foreach($this->items as &$item)  {
                if($product['id'] == $item['id']) {
                    $item['quantity'] = $quantity;
                    $item['subtotal'] = ($product['price'] * $quantity);
                    $this->calculateTotal();
                }
            }

        }
    }

    public function removeFromCart($product) {
        if($this->hasItems()) {
            $i = -1;
            foreach($this->items as $item) {
                $i++;
                if($product['id'] == $item['id']) {
                    unset($this->items[$i]);
                    $this->calculateTotal();
                }
            }
        }
    }

    public function addToCart($product, $quantity) {
        if($quantity < 1) {
            return;
        }
        if($this->isInCart($product)) {
            return;
        }
        $item = [
            'id' => $product['id'],
            'name' => $product['name'],
            'description' => $product['description'],
            'price' => $product['price'],
            'quantity' => $quantity,
            'subtotal' => ($product['price'] * $quantity)
        ];
        $this->items[] = $item;
        $this->calculateTotal();
    }

    private function isInCart($product) {
        if( $this->hasItems()) {
            foreach( $this->items as $item ) {
                if($item['id'] == $product['id']) {
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }

    private function calculateTotal() {
        $this->total = 0.00;
        if($this->hasItems()) {
            $tot = 0.00;
            foreach($this->items as $item) {
                $tot += $item['subtotal'];
            }
            $this->total = $tot;
        }
    }

    public function hasItems() {
        return ( count( $this->items ) > 0 );
    }
}

Adotteremo l'approccio procedurale per quanto riguarda le operazioni da frontend sul carrello. In questo modo tutti i passaggi potranno essere compresi al meglio.

Per farlo occorre definire una serie di funzioni helper per poter operare sui dati del carrello.

require_once 'connection.php';

function get_product($id) {
    global $db;
    $product = $db->getResult("SELECT id,name,description,price FROM products WHERE id = $id");
    if(count($product) === 0) {
        return null;
    }
    return [
        'id' => $product['id'],
        'name' => $product['name'],
        'description' => $product['description'],
        'price' => floatval($product['price'])
    ];
}

function save_cart(Cart $cart) {
    $_SESSION['cart'] = $cart->getCart(true);
}

function have_cart() {
    return isset($_SESSION['cart']);
}

function cart_redirect() {
    header('Location: cart.php');
    exit;
}

Nella pagina del carrello, dobbiamo innanzitutto instanziare la classe Cart in modo condizionale: se la sessione corrente non ha un'instanza del carrello, possiamo usare una semplice istanza della classe. Altrimenti sincronizzeremo i dati della sessione con l'istanza della classe.

session_start();
require_once 'lib/Cart.php';
require_once 'lib/functions.php';

$cart = null;
if( have_cart() ) {
    $cart = new Cart();
    $session_cart = unserialize($_SESSION['cart']);
    $cart->setItems($session_cart['items']);
    $cart->setTotal($session_cart['total']);
} else {
    $cart = new Cart();
}

La prima operazione da implementare è l'aggiunta di un prodotto al carrello. Ricordiamo che la funzione get_product() restituisce un array associativo contenente i dati di un prodotto presi dal database.

if(isset($_POST['add-to-cart'])) {
    $product = get_product($_POST['product_id']);
    if(!is_null($product)) {
        $cart->addToCart($product, intval($_POST['quantity']));
        save_cart($cart);
        cart_redirect();
    }
}

La seconda operazione da implementare è la rimozione di un prodotto dal carrello.

if(isset($_GET['remove-id'])) {
    $product = get_product($_GET['remove-id']);
    $cart->removeFromCart($product);
    save_cart($cart);
    cart_redirect();

}

La terza operazione da implementare è l'aggiornamento del carrello. Questa operazione richiede che gli elementi input contenenti le quantità dei prodotti vengano scritti con una sintassi particolare nella pagina del carrello.

<?php if( have_cart() && $cart->hasItems() ):

        ?>

        <form action="cart.php" method="post">
            <table class="table table-bordered">
                <thead>
                    <th scope="col">Product Name</th>
                    <th scope="col">Quantity</th>
                    <th scope="col">Price</th>
                    <th scope="col" colspan="2">Subtotal</th>
                </thead>
            <tbody>
                <?php foreach($cart->getItems() as $item): ?>
                    <tr>
                        <td><?= $item['name']; ?></td>
                        <td><input type="number" class="form-control" name="quantity[<?= $item['id']; ?>]" value="<?= $item['quantity']; ?>"></td>
                        <td>&euro; <?= str_replace( '.', ',', $item['price'] ); ?></td>
                        <td>&euro; <?= str_replace( '.', ',', $item['subtotal'] ); ?></td>
                        <td><a href="cart.php?remove-id=<?= $item['id']; ?>" class="text-danger">Remove</a></td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
            </table>
            <div class="mt-4 d-flex justify-content-between">
                <input type="submit" name="update-cart" value="Update Cart" class="btn btn-outline-primary">
                <span>
                    <strong>Total:</strong> &euro; <?= number_format($cart->getTotal(), '2', ',', '.'); ?>
                </span>
            </div>
        </form>

    <?php else: ?>

    <p class="alert alert-info">Your cart is empty.</p>

    <?php endif; ?>

La sintassi <input name="quantity[id]" value="[qty]"> crea l'array associativo quantity nell'array globale $_POST dove le chiavi saranno gli ID dei prodotti e i valori le quantità degli stessi prodotti eventualmente modificate dall'utente.

Possiamo quindi scrivere:

if(isset($_POST['update-cart'])) {
    foreach($_POST['quantity'] as $id => $qty) {
        $product = get_product($id);
        $cart->updateCart($product, intval($qty));
    }
    save_cart($cart);
    cart_redirect();
}

Come si può notare, una volta definita la classe che rappresenta il carrello, implementare le operazioni su di esso è un'operazione relativamente semplice.