gb2016
gb2016

Reputation: 111

Replacing in (or adding to) multidimensional associative array dependent upon multiple conditions

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

Answers (1)

Rasclatt
Rasclatt

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

Related Questions