Reputation: 111
I have created a cart using a multidimensional associative array. New items can be added and items which already exist have their quantities and total dollar amount updated. The cosmetic condition of each item must be checked and items with a unique condition are added to the cart as a new item.
I wrote the code below, and it does work, but it seems overly complicated. Is there a better way to get this done?
// if there are items in the cart
if(isset($_SESSION['cart'])) {
// loop through the array
for($i = 0; $i <= count($_SESSION['cart']); $i++) {
// if $name is found in $_SESSION['cart'] array ...
if(in_array_r($name, $_SESSION['cart'])) {
// if $i == count($_SESSION['cart']) that means a new item needs to be added since $name
// was found in the array but no match for $condition was found
if($i == count($_SESSION['cart'])) {
$_SESSION['cart'][] = array('name' => $name, 'condition' => $condition, 'quantity' => $quantity, 'itemTotal' => $itemTotal);
break;
}
// if a match for $name and $condition is found update the quantity and totals for that item
elseif($name == $_SESSION['cart'][$i]['name'] && $condition == $_SESSION['cart'][$i]['condition']) {
$_SESSION['cart'][$i]['quantity'] = $_SESSION['cart'][$i]['quantity'] + $quantity;
$_SESSION['cart'][$i]['itemTotal'] = $_SESSION['cart'][$i]['itemTotal'] + $itemTotal;
break;
}
// continue through the loop to search for a match
else {
continue;
}
}
// if $name isn't found in array add new item
else {
$_SESSION['cart'][] = array('name' => $name, 'condition' => $condition, 'quantity' => $quantity, 'itemTotal' => $itemTotal);
break;
}
}
// sort in alphabetical order
sort($_SESSION['cart']);
}
// if there are no items in the cart
else {
$_SESSION['cart'][] = array('name' => $name, 'condition' => $condition, 'quantity' => $quantity, 'itemTotal' => $itemTotal);
}
// recursive in_array function
function in_array_r($needle, $haystack, $strict = false) {
foreach ($haystack as $item) {
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && in_array_r($needle, $item, $strict))) {
return true;
}
}
return false;
}
Upvotes: 0
Views: 97
Reputation: 12505
I would propose you should make your carting into a class
for easy compiling and implementation site-wide. This is just a basic example and I am not proposing using this or replacing what you have, see if it's of any value, but it may give you some ideas:
/classes/class.Cart.php
// Create an easy way to access your cart app
class Cart
{
private static $singleton;
private static $name;
// Allows for same-resource re-use
public static function app()
{
self::$name = (empty(self::$name))? 'cart' : self::$name;
if(empty(self::$singleton))
self::$singleton = new ShoppingCart(self::$name);
return self::$singleton;
}
public static function init($name = 'cart')
{
self::$name = $name;
}
}
/classes/class.ShoppingCart.php
class ShoppingCart
{
public $name;
public $condition;
public $total;
public $items;
private $cart;
private $cart_info;
private $item;
const REV_SORT = 'r';
const NAT_SORT = 'n';
// The construct will allow for using mulitple carts if required
public function __construct($name = 'cart')
{
$this->cName = $name;
// This will save the cart data but it only holds the id and qty
$this->cart = (!empty($_SESSION[$this->cName]))? $_SESSION[$this->cName] : array();
// This will save a tandem session that only stores static data in reference to items added to the cart
$this->cart_info = (!empty($_SESSION[$this->cName."_info"]))? $_SESSION[$this->cName."_info"] : array();
}
// This will retrieve a cart item's total value (qty x price)
public function getItemTotal($item = false)
{
if(empty($_SESSION[$this->cName][$item]))
return 0;
// Filter all but numbers and decimals
$price = (!empty($_SESSION[$this->cName."_info"][$item]['price']))? preg_replace('/[^0-9\.]/',"",$_SESSION[$this->cName.'_info'][$item]['price']) : false;
// Check if qty is numeric, if not, just add qty as 1
$qty = (!empty($_SESSION[$this->cName][$item]['qty']) && is_numeric($_SESSION[$this->cName][$item]['qty']))? $_SESSION[$this->cName][$item]['qty'] : 1;
// Return the final price back. "invalid" means something was not submitted properly
return (!empty($price))? ($_SESSION[$this->cName.'_info'][$item]['price'] * $qty) : 'invalid';
}
// This will fetch the total cart value
public function getCartTotal($format = false)
{
if(empty($_SESSION[$this->cName.'_info']))
return 0;
// Save subtotals to new array
foreach($_SESSION[$this->cName.'_info'] as $itm) {
$sub[] = $itm['subtotal'];
}
// Sum the array
$final = (!empty($sub))? array_sum($sub) : 0;
// If the format is true, send back a currency value
if($format) {
// This may draw error if on a WINDOWS machine.
// May have to use number_format() instead and possibly strpad()
setlocale(LC_MONETARY, 'en_US');
return money_format("%#10n",$final);
}
// $format false will send back just the unformatted value
else
return $final;
}
public function getItemList($sort = 'n')
{
// Check if the session is set
if(empty($_SESSION[$this->cName]))
return false;
// Loop through the items and send back decoded names
$arr = array_map(function($v) { return base64_decode($v); },array_keys($_SESSION[$this->cName]));
// Check if there is a sort order specified
switch($sort) {
case('n'):
// This may draw error if your PHP version is too low
asort($arr,SORT_NATURAL);
break;
case('r'):
arsort($arr);
break;
default:
sort($arr);
}
return $arr;
}
// This will return item details if it exists
public function getDetails($item = false)
{
$item = base64_encode($item);
if(isset($_SESSION[$this->cName.'_info'][$item]))
return (object) $_SESSION[$this->cName.'_info'][$item];
return false;
}
// This removes the details of a stored item
public function removeDetails()
{
if(isset($_SESSION[$this->cName.'_info'][$this->item])) {
unset($_SESSION[$this->cName.'_info'][$this->item]);
unset($this->cart_info[$this->item]);
}
}
// This sets the items calculated subtotal price (qty x price)
public function saveSubTotal()
{
return (!empty($_SESSION[$this->cName.'_info'][$this->item]['price']))? $this->getItemTotal($this->item) : 'invalid';
}
// This saves the items details. I use the $_GET details, but you can use data pulled from the DB or whatever
public function saveDetails($details = array())
{
if(isset($_SESSION[$this->cName][$this->item])) {
if(!isset($_SESSION[$this->cName.'_info'])) {
$this->cart_info = $_SESSION[$this->cName.'_info'] = array();
}
$_SESSION[$this->cName.'_info'][$this->item] = $details;
$_SESSION[$this->cName.'_info'][$this->item]['subtotal'] = $this->saveSubTotal();
$this->cart_info[$this->item] = $_SESSION[$this->cName.'_info'][$this->item];
}
}
// This adds to cart
public function addToCart($item = false, $qty = 1)
{
// It would be better just to use the id from your database, then it's unique and easy
// but you are using name, so I am creating a generated id
$this->item = base64_encode($item);
// Create cart
if(!isset($_SESSION[$this->cName]))
$this->cart = $_SESSION[$this->cName] = array();
// Add qty
if(!isset($_SESSION[$this->cName][$this->item]))
$_SESSION[$this->cName][$this->item]['qty'] = $qty;
// or Update qty
else
$_SESSION[$this->cName][$this->item]['qty'] += $qty;
// Assign to a local variable
$this->cart[$this->item] = $_SESSION[$this->cName][$this->item];
// Return the object to use in method chaining
return $this;
}
// This allows you to remove from the cart either by subtracting qty or by remove totally
public function removeFromCart($item = false,$amt = false)
{
$this->item = base64_encode($item);
if(!isset($_SESSION[$this->cName][$this->item]))
return;
else {
if(is_numeric($amt)) {
$curr = (int) $_SESSION[$this->cName][$this->item]['qty'];
$amt = (int) $amt;
if(($curr - $amt) >= 0.01) {
$this->cart_info[$this->item]['qty'] = $_SESSION[$this->cName][$this->item]['qty'] = ($amt - $curr);
$_SESSION[$this->cName.'_info'][$this->item]['subtotal'] = $this->saveSubTotal();
return;
}
}
unset($_SESSION[$this->cName][$this->item]);
unset($this->cart[$this->item]);
$this->removeDetails();
}
}
// Clear the entire cart
public function clearCart()
{
if(isset($_SESSION[$this->cName])) {
$this->cart = array();
$this->cart_info = array();
unset($_SESSION[$this->cName]);
unset($_SESSION[$this->cName."_info"]);
}
}
// Set the quantity based on validation of array parameter
public function setQty($array = false)
{
return ((!empty($array['qty']) && is_numeric($array['qty'])) && $array['qty'] !== 0)? $array['qty'] : 1;
}
// Set name based on parameter
public function setName($array = false)
{
return (!empty($array['name']))? $array['name'] : false;
}
}
/index.php
// Include the above classes
include_once(__DIR__."/classes/class.Cart.php");
include_once(__DIR__."/classes/class.ShoppingCart.php");
// sets a the cart name, by default it's set to "cart"
// Cart::init("cart2");
// I am using $_GET, you can use $_POST if you want
// If add or remove
if(isset($_GET['add']) || isset($_GET['remove'])) {
// Set defaults
$qty = Cart::app()->setQty($_GET);
$name = Cart::app()->setName($_GET);
// If the name is empty, just stop
if(empty($name))
return false;
// If adding, add to cart, save the item's details
if(isset($_GET['add']))
Cart::app()->addToCart($name,$qty)->saveDetails($_GET);
// Else, you can remove
// Leave second parameter blank if you want remove to just
// straight remove the item from cart
// otherwise it will subtract if the $qty is numeric and not "false"
else
Cart::app()->removeFromCart($name,$qty);
}
// Clear will remove the cart completely
// including the details
elseif(isset($_GET['clear']))
Cart::app()->clearCart();
// get data for just the item 1
print_r(Cart::app()->getDetails("item1"));
// Get the list ordered naturally
print_r(Cart::app()->getItemList(ShoppingCart::NAT_SORT));
// Get the cart total
// Using true will format the output (see this method for more info if error occurs)
echo Cart::app()->getCartTotal(true);
// Show session
print_r($_SESSION);
?>
<a href="?add=true&name=item1&qty=1&condition=good&price=23.99">ADD ITEM1</a><br />
<a href="?add=true&name=item2&qty=1&condition=crap&price=24.50">ADD ITEM2</a><br />
<a href="?add=true&name=item3&qty=1&condition=horrible&price=34.50">ADD ITEM3</a><br />
<a href="?add=true&name=item4&qty=1&condition=super&price=11.33">ADD ITEM4</a><br />
<a href="?remove=true&name=item1">REMOVE ITEM1</a><br />
<a href="?remove=true&name=item2">REMOVE ITEM2</a><br />
<a href="?remove=true&name=item<?php echo $nm = rand(1,2); ?>&qty=10">REMOVE ITEM<?php echo $nm;?></a><br />
<a href="?remove=true&name=item<?php echo $nm = rand(3,4); ?>&qty=10">REMOVE ITEM<?php echo $nm;?></a><br />
<a href="?clear=true">Clear</a><br />
OUTPUT:
// item1 details
stdClass Object
(
[add] => true
[name] => item1
[qty] => 1
[condition] => good
[price] => 23.99
[subtotal] => 23.99
)
// ordered list
Array
(
[1] => item1
[0] => item4
)
// Cart total
$35.32
// cart session details
Array
(
[cart] => Array
(
[aXRlbTQ=] => Array
(
[qty] => 1
)
[aXRlbTE=] => Array
(
[qty] => 1
)
)
[cart_info] => Array
(
[aXRlbTQ=] => Array
(
[add] => true
[name] => item4
[qty] => 1
[condition] => super
[price] => 11.33
[subtotal] => 11.33
)
[aXRlbTE=] => Array
(
[add] => true
[name] => item1
[qty] => 1
[condition] => good
[price] => 23.99
[subtotal] => 23.99
)
)
)
I should also note that the addToCart()
combined with the saveDetails()
method are doing roughly the same thing as your script, I just split them into two parts.
Upvotes: 1