Check if the product exists in the shopping cart - Cakephp 3

Friends, I appreciate if anyone can analyze.

I add items to the shopping cart according to the code below.

    public function add()
    {

        $order = $this->Orders->newEntity();

        if ($this->request->is('post')) {

            $order = $this->Orders->patchEntity($order, $this->request->getData());
            $order->product = $this->Orders->Products->get($order->product_id, ['contain' => ['Users']]);
            $session = $this->request->getSession();
            $cart = $session->read('cart');
            $cart[] = $order;


            if(count($cart) <= 3 ){

                $session->write('cart', $cart);
                $this->Flash->success(__('OK'));
                return $this->redirect(['action' => 'index']);

            }elseif ($this->Auth->user()){

                $session->write('cart', $cart);
                $this->Flash->success(__('OK'));
                return $this->redirect(['action' => 'index']);

            }else{
               $this->Flash->success(__('Error'));
               return $this->redirect('/usuarios/login');
           }

           $product = $this->Orders->Products->find('list', ['limit' => 200]);
           $users = $this->Products->Users->find('list', ['limit' => 200]);
           $this->set(compact('order', 'products', 'users'));
       }
   }

This works well. But I would like to check if the product sent already exists in the cart. As it is, the same product can be sent multiple times.

I was a little confused to do this in cakephp using session. I tried to use:

if ($cart->check('name')) {
// name exists and is not null.
}

I just don’t understand how to access the array index to compare if the product_id value already exists.

Thanks for any comment!

You don’t say where you are trying to do $cart->check('name'). I’m guessing it is something you want include in new add() method logic?

However, in the add() method, $cart is an array but you are trying to use it as an object by running its check() method (which does not exist).

One way you could go, is to key your $cart array with the product id rather than letting it be a sequential numeric index as you are currently.

//set $order as you have in your existing code

//get the session as you are currently doing
$session = $this->request->getSession();
$cart = $session->read('cart');

//change the way you put items on the cart array
if (isset($cart[$order->product_id])) {
   //product is already in the cart
   //take whatever action your business rules suggest
} else {
   $cart[$order->product_id] = $order;
}

You also might consider going a step further and making a Cart object that encapsulates the basic features of your cart. This will decouple your cart implementation from the rest of your code. This could be a simple class that did not extend anything else.

I’ve sketched one out that will insert cart items and return an array of all the items.

Also after inserting your can get a result boolean and success/error message from the object.

Suppose you build it in a new directory Lib (App\Lib)

Code for the example Cart class
<?php
namespace App\Lib;

use Cake\ORM\Entity;

/**
 * Simple example Cart Class
 *
 * @package App\Lib
 */
class Cart
{
    /**
     * @var bool
     */
    protected $result;

    /**
     * @var string message from last action
     */
    protected $message = 'No cart action has been taken';

    /**
     * @var array
     */
    protected $cart_contents = [];

    /**
     * Place the entity in the the cart
     *
     * Key by entity->id by default. Use a provided
     * key otherwise
     *
     * @param Entity $product entity containing a
     * @param null|int|string $key
     */
    public function insert($product, $key = null)
    {
        $this->clearResult();
        if (!isset($product->id) && is_null($key)) {
            $this->setResult(
                false,
                '\'id\' was not found on the entity and no other key was provided'
            );
        }

        $key = $key ?? $product->id;

        //do other insertion logic here for example
        if(isset($this->cart_contents[$key])) {
            $this->setResult(false, 'The item is already in the cart');
        } else {
            $this->cart_contents[$key] = $product;
            $this->setResult(true, 'The item was added to the cart');
        }

        return $this;
    }

    /**
     * Return the current cart contents
     *
     * Messaging is set to 'no action' since we don't return
     * this object and so the result can't be read from the return value
     * @return array
     */
    public function getContents()
    {
        $this->clearResult();
        return $this->cart_contents;
    }


    /**
     * If no action has been performed return false
     *
     * @return bool
     */
    public function result()
    {
        return $this->result ?? false;
    }

    /**
     * The message from the last action
     *
     * @return string
     */
    public function message()
    {
        return $this->message;
    }
    /**
     * Set/reset the default state of the object's activity reporting
     */
    protected function clearResult()
    {
        $this->result = null;
        $this->message = 'No cart action has been taken';
    }

    /**
     * Create a report for the outside world
     *
     * @param bool $result
     * @param string $message
     */
    protected function setResult($result, $message)
    {
        $this->result = $result;
        $this->message = $message;
    }
}

With this class you simplify the logic in your controller and make things easier to read. Revising the code from my previous answer:

//set $order as you have in your existing code

//get the session as you are currently doing
$session = $this->request->getSession();

// if there is no cart on the session yet make a new one
$cart = $session->read('cart') ?? new Cart();

$cart->insert($order);
// failure to insert still returns a valid cart, always store it
$session->write('cart', $cart);

if($cart->result() {
   $this->Flash->success($cart->message();
} else {
   $this->Flash->error($cart->message();
}

And if the business rules for how you handle duplicates changes, your controller code will not have to be modified. You could for example increase the number of a single product in the cart and send a message back saying ‘There are now x widgits in the cart.’

When executed it gives error:

Call to a member function insert() on array

I created the Lib directory at the root of the project and also in the src folder to test. And then the cart.php class. Where should I create the directory? I get this error above in the insert () method.

Well, none of the code I wrote was tested. It was only an example I threw together to give you an idea about how you might approach this problem.

If you actually want to go down this path, you will have a little work to do I’m sure.

But the error you are seeing sounds like it is related to the result of your Session->read. It has returned an array rather than an object. This might be related to some leftover data in the session from your previous code…

You can debug the value returned from the session and see what it is and how you should actually be using it.

The way I set the namespace in my sample class means the file should be at:

your-app-path/src/Lib

here is an example from my project:

Thanks for the explanation and the sample code. I will try to see what is going on with this code.

I’ll be interested in any observations you have regarding the viability of the class. I plan on expanding soon for a project I’m working on that needs a simple little shopping cart.

Thank you. I ended up solving it this way:

$counter = 0;
            foreach((array) $cart as $cartOne){

                if($cartOne['product_id'] == $order->product_id){
                    $cartOne['quantity'] += 1;
                    $counter++;
                    break;
                } 
            }

I will test it the way you indicated too. Thank you