How to increment the session array value?

I am using this on a shopping cart

if (!empty($_getvars['id'])) {
    $data = $session->get('cart');
    $data[] = $_getvars['id'];
    $session->set('cart', $data);
} 

$_getvars['id'] is productid, and on each click, a new array element will be added to the session. It works fine as it is now, but if a product is chosen more than once a new array will be added, how can change it that productid will be array offset and the value will be incremented from 1 each time to reflect the quantity?

$i = 1;
if (!empty($_getvars['id'])) {
    $data = $session->get('cart');
    $data[$_getvars['id']] = $i++;
    $session->set('cart', $data);
} 

but this code each time resets to 1. How to fix it? Or any better array structure for a shopping cart?

Try the following:

$data[$_getvars['id']] = ++ $i;

// or

$data[$_getvars['id']] = $i = 1+ $i;

How your code know the value of retrieved session array to increment it?

It doesnt need to.

++ increments by 1.

the code should do

data[$_getvars['id']]++;

Unless data[$_getvars['id']] isn’t set yet, because they you get PHP Notice: Undefined index and data[$_getvars['id']] is set to NULL, not 1.

Also, I would encapsulate the shopping cart logic into it’s own class.

Something like this:

final class ShoppingCart
{
    /**
     * @var ShoppingCartStorage
     */
    private $storage;

    public function __construct(ShoppingCartStorage $storage)
    {
        $this->storage = $storage;
    }

    public function addProduct(string $productId, int $quantity = 1): void
    {
        if ($quantity < 1) {
            throw new RuntimeException('Number of products to order is at least 1');
        }
        $this->storage->add($productId, $quantity);
    }

    public function removeProduct(string $productId, int $quantity = 1): void
    {
        $quantityInCart = $this->storage->getQuantity($productId);

        if ($quantityInCart - $quantity < 0) {
            throw new RuntimeException(
                sprintf(
                    'Unable to remove %d items from cart - would result in negative amount of items in cart',
                    $quantity
                )
            );
        }

        $this->storage->remove($productId, $quantity);
    }

    public function getContents(): array
    {
        return $this->storage->all();
    }

    public function empty(): void
    {
        $this->storage->clear();
    }
}

interface ShoppingCartStorage
{
    public function add(string $productId, int $quantity): void;

    public function remove(string $productId, int $quantity);

    public function all(): array;

    public function clear(): void;

    public function getQuantity(string $productId): int;
}

final class ArrayStorage implements ShoppingCartStorage
{
    private $data;

    public function add(string $productId, int $quantity): void
    {
        if (!isset($this->data[$productId])) {
            $this->data[$productId] = $quantity;

            return;
        }

        $this->data[$productId] += $quantity;
    }

    public function remove(string $productId, int $quantity): void
    {
        $this->data[$productId] -= $quantity;

        if ($this->data[$productId] === 0) {
            unset($this->data[$productId]);
        }
    }

    public function all(): array
    {
        return $this->data;
    }

    public function clear(): void
    {
        $this->data = [];
    }

    public function getQuantity(string $productId): int
    {
        return $this->data[$productId] ?? 0;
    }
}

To test:

function listCartContents(ShoppingCart $cart) {
    echo 'Cart contents:', PHP_EOL;
    foreach ($cart->getContents() as $productId => $quantity) {
        printf(' - %d x %s ', $quantity, $productId);
        echo PHP_EOL;
    }
    echo PHP_EOL;
}

$cart = new ShoppingCart(new ArrayStorage());

echo 'Add 1x Product 123', PHP_EOL;
$cart->addProduct('123', 1);
echo 'Add 2x Product 123', PHP_EOL;
$cart->addProduct('123', 2);
listCartContents($cart);

echo 'Remove 1x Product 123', PHP_EOL;
$cart->removeProduct('123', 1);
listCartContents($cart);

echo 'Add 3x Product 456', PHP_EOL;
$cart->addProduct('456', 3);
listCartContents($cart);

echo 'Remove 3x Product 456', PHP_EOL;
$cart->removeProduct('456', 3);
listCartContents($cart);

echo 'Clear cart', PHP_EOL;
$cart->empty();

listCartContents($cart);

The advantage is that how you interact with the ShoppingCart is how you would interact with one in the real world; you just put stuff in and get stuff out. How many there are isn’t important for using the shopping cart.

Also, when you do it like this, it will be a lot easier to swap storage from the session to for example a database later if you wanted to.

Of course if you want to use this with the session you need to write a class that implements SessionCartStorage for the session. I’ll leave that as an exercise for the reader :wink:

Wow it worked great! You are f****ing genious! And a great helper. Thanks.

Here is session storage:

final class SessionStorage implements ShoppingCartStorage
{
    private $data;
    private $session;

    public function __construct($session)
    {
        $this->session = $session;
        $this->data = $this->session->get('cart');
}

    public function add(string $productId, int $quantity): void
    {
        if (!isset($this->data[$productId])) {
            $this->data[$productId] = $quantity;
            $this->session->set('cart', $this->data);
            return;
        }
            $this->data[$productId] += $quantity;
            $this->session->set('cart', $this->data);
    }

    public function remove(string $productId, int $quantity): void
    {
        $this->data[$productId] -= $quantity;
        $this->session->set('cart', $this->data);

        if ($this->data[$productId] === 0) {
            unset($this->data[$productId]);
            $this->session->set('cart', $this->data);

        }
    }

    public function all(): array
    {
        return $this->data;
    }

    public function clear(): void
    {
            $this->session->remove('cart');
    }

    public function getQuantity(string $productId): int
    {
        return $this->data[$productId] ?? 0;
    }
}

Here is the output with print session debug

Add 1x Product 123
Array
(
[123] => 1
)
Add 2x Product 123
Cart contents:

  • 3 x 123

Array
(
[123] => 3
)
Remove 1x Product 123
Cart contents:

  • 2 x 123

Array
(
[123] => 2
)
Add 3x Product 456
Cart contents:

  • 2 x 123
  • 3 x 456

Array
(
[123] => 2
[456] => 3
)
Remove 3x Product 456
Cart contents:

  • 2 x 123

Array
(
[123] => 2
)
Clear cart
Cart contents:

  • 2 x 123

It is working correct, but any improvements?

And what about update cart on checkout? for example if he has chosen 3x of an item then on checkout he choose 1x from spinner? so it should use (3 -1 = 2) to remove 2x from the cart? and what about adding more? so it should first check the value of spinner if this is lower than cart, it uses - (cart - spinner) and if this is greater, it uses the reverse (spinner - cart) e.g. 4-3 = 1 to add one more? can you give the math code to do this all? Or better to just remove that item completely with the code below in ShoppingCart then add the amount of spinner?

public function clearProduct(string $productId): void
{
    $this->storage->remove($productId, $this->storage->getQuantity($productId));
}

With the session you don’t need the data property any more. Makes it a lot simpler.

You’re already getting bitten by this, because clear only clears the session, but not the data variable, so after you clear and then get the contents it’s still there, but it shouldn’t be.

updateProductQuantity(string $productId, int $quantity): void

Should do the trick for all your questions. Add a similar method to the storage as well.

  1. As you said there is no need to data property so how to get session values to modify and update them as we had below?

$this->data = $this->session->get('cart');

  1. And how to have total item for the badge in top navbar? Should I foreach the session array to sum the total items or is there a better way? Like this?
 public function getTotal() : void
{
foreach ($this->data as $product => $quantity) {
$total += $quantity;
}
$this->session->set('badge', $total);
}

Then call this method in addProduct to have the badge always updated. Correct or any better way?

  1. Your code
echo 'Clear cart', PHP_EOL;
$cart->empty();

listCartContents($cart);

Is still printing 2 × 123 as it was the previous quantity however the session was cleared correctly. So why is it printing this?

  1. As about your updateProductQuantity() I meant to ask if this is better to do the math as I said previously or just remove all quantities of that item and then add again with the quantity? Like this? Is there a better way?
public function clearProduct(string $productId): void
{
    $this->storage->remove($productId, $this->storage->getQuantity($productId));
}

public function updateProductQuantity(string $productId, int $quantity): void
{
  $this->clearProduct($productId);
  $this->addProduct($productId, $quantity);
}

PS. As we don’t know if user is adding or removing items from spinner, I guess this is better to not call addProduct() directly and instead call updateProductQuantity() as I gave above? right? but it causes undefined offset notice for the first time, so I added IF clause to remove() as below to avoid notice:

    public function remove(string $productId, int $quantity): void
    {
        if (isset($this->data[$productId])) {
            $this->data[$productId] -= $quantity;
            $this->session->set('cart', $this->data);
            if ($this->data[$productId] === 0) {
                unset($this->data[$productId]);
               $this->session->set('cart', $this->data);
            }
        }
    }

Please let me know if I am correct and help to imrove these all codings. Thanks.

Every time you need some data from the session, read it from the session :slight_smile:

Well, first of, you need to define “number of items in basket”. Most sites use “Number of distinct products in basket”, so that I have 5 of the same t-shirt and a pair of pants, the badge would have 2, because I have 2 distinct products, even though I’ve got 6 items.

I would however not set this value in the session, just return in. When you set it in the session you create something called a temporal coupling where you have to call one method (in this case, $cart->getTotal()) before you call something else (in this case, $session->get('badge');). What is problematic about this is that when you forget to call $cart->getTotal() before $session->get('badge'); you may get an older value that is longer actual. If you always ask the shopping for its total directly you don’t have this problem.

Because you cleared the session, but not $data. This is why I recommended you not to keep $data in there, it only adds confusion and has no benefit whatsoever. Either work only with $data, like I did in my MemoryCartStorage, or only work with the session, or only work with something else, but don’t mix and match.

I would also add a method to the storage to set the quantity of a product. Juggling all the math seems pointless and tedious.

With all these things, think about the use cases first. What does the user want to with the cart? Well, they might want to add stuff to it, or update the amount, or remove a product completely, stuff like that. Those are the methods you should implement on your ShoppingCart object. Which methods you need for your website depends on what interaction you allow for the user. Maybe on your website there is no option to remove n items from a product. If that’s the case, don’t create a method for that.

In any case, always try and make interaction with an object as simple as possible. Do not overcomplicate stuff, like calculating how many items should be removed to get to a certain amount and then remove that many items from a product. Just update the quantity for the product.

Always first start with the question “As a user of this object, what problem(s) would I like this object to solve for me?”. That should give you a list of methods that object should have. Once you have those you can think about implementing those methods. Starting with just coding never works in the long run if there isn’t some sort of design behind it.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.