DEV Community

Cover image for Managing the Cart Storage | Building a Shopping Cart with Symfony
Quentin Ferrer
Quentin Ferrer

Posted on • Edited on

Managing the Cart Storage | Building a Shopping Cart with Symfony


Each user will have their own cart. As soon as a product is added to the cart, an Order is created in a database and then associated with the user via an Order ID in the session.

We will not manage user accounts, so the cart will be always associated with an anonymous user - a user who does not have a user account.

Creating the Session Storage

The cart has to be stored in the session to keep the items in the user's cart during their visit. Once the session has expired, the cart will be cleared. A new cart will be created and stored when the user adds a product to the cart.

Create a CartSessionStorage service to manage the cart in the session and add the following operations:

  • Add a setCart() method to save the cart in the cart_id session key by using the cart ID,
  • Add a getCart() method to retrieve the cart in the session. Get the cart ID from the cart_id session key, fetch the cart object by using the OrderRepository repository, and return it.
<?php

namespace App\Storage;

use App\Entity\Order;
use App\Repository\OrderRepository;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

class CartSessionStorage
{
    /**
     * The request stack.
     *
     * @var RequestStack
     */
    private $requestStack;

    /**
     * The cart repository.
     *
     * @var OrderRepository
     */
    private $cartRepository;

    /**
     * @var string
     */
    const CART_KEY_NAME = 'cart_id';

    /**
     * CartSessionStorage constructor.
     *
     * @param RequestStack $requestStack
     * @param OrderRepository $cartRepository
     */
    public function __construct(RequestStack $requestStack, OrderRepository $cartRepository)
    {
        $this->requestStack = $requestStack;
        $this->cartRepository = $cartRepository;
    }

    /**
     * Gets the cart in session.
     *
     * @return Order|null
     */
    public function getCart(): ?Order
    {
        return $this->cartRepository->findOneBy([
            'id' => $this->getCartId(),
            'status' => Order::STATUS_CART
        ]);
    }

    /**
     * Sets the cart in session.
     *
     * @param Order $cart
     */
    public function setCart(Order $cart): void
    {
        $this->getSession()->set(self::CART_KEY_NAME, $cart->getId());
    }

    /**
     * Returns the cart id.
     *
     * @return int|null
     */
    private function getCartId(): ?int
    {
        return $this->getSession()->get(self::CART_KEY_NAME);
    }

    private function getSession(): SessionInterface
    {
        return $this->requestStack->getSession();
    }
}
Enter fullscreen mode Exit fullscreen mode

Creating the Cart Manager

Create the CartManager class that helps us retrieve the current cart of a user and saving the cart.

<?php

namespace App\Manager;

/**
 * Class CartManager
 * @package App\Manager
 */
class CartManager
{
}
Enter fullscreen mode Exit fullscreen mode

Retrieving the current cart

For each action performed on the cart, we will need to retrieve the current cart:

  • If the cart is already associated with the user in the session, it's the current cart,
  • If the cart does not exist in the session, a new cart is created and becomes the current cart. It will be associated with the user in the session after being persisted in the database.

Add a getCurrentCart() method to the CartManager and retrieve the current cart. Get the cart already associated with the user using the CartSessionStorage. If the cart does not exist, create a new cart by using the OrderFactory factory. Finally, return the cart.

<?php

namespace App\Manager;

use App\Entity\Order;
use App\Factory\OrderFactory;
use App\Storage\CartSessionStorage;

/**
 * Class CartManager
 * @package App\Manager
 */
class CartManager
{
    /**
     * @var CartSessionStorage
     */
    private $cartSessionStorage;

    /**
     * @var OrderFactory
     */
    private $cartFactory;

    /**
     * CartManager constructor.
     *
     * @param CartSessionStorage $cartStorage
     * @param OrderFactory $orderFactory
     */
    public function __construct(
        CartSessionStorage $cartStorage,
        OrderFactory $orderFactory
    ) {
        $this->cartSessionStorage = $cartStorage;
        $this->cartFactory = $orderFactory;
    }

    /**
     * Gets the current cart.
     * 
     * @return Order
     */
    public function getCurrentCart(): Order
    {
        $cart = $this->cartSessionStorage->getCart();

        if (!$cart) {
            $cart = $this->cartFactory->create();
        }

        return $cart;
    }
}

Enter fullscreen mode Exit fullscreen mode

Saving the Cart

Rather than storing the whole object in the session, we will persist the cart in the database to associate it with the user by using the Order ID.

Add a save() method to the CartManager, persist the cart in the database by using the EntityManager service, and store the cart in session by using the CartSessionStorage service.

<?php

namespace App\Manager;

use App\Entity\Order;
use App\Factory\OrderFactory;
use App\Storage\CartSessionStorage;
use Doctrine\ORM\EntityManagerInterface;

/**
 * Class CartManager
 * @package App\Manager
 */
class CartManager
{
    /**
     * @var CartSessionStorage
     */
    private $cartSessionStorage;

    /**
     * @var OrderFactory
     */
    private $cartFactory;

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * CartManager constructor.
     *
     * @param CartSessionStorage $cartStorage
     * @param OrderFactory $orderFactory
     * @param EntityManagerInterface $entityManager
     */
    public function __construct(
        CartSessionStorage $cartStorage,
        OrderFactory $orderFactory,
        EntityManagerInterface $entityManager
    ) {
        $this->cartSessionStorage = $cartStorage;
        $this->cartFactory = $orderFactory;
        $this->entityManager = $entityManager;
    }

    // ...

    /**
     * Persists the cart in database and session.
     *
     * @param Order $cart
     */
    public function save(Order $cart): void
    {
        // Persist in database
        $this->entityManager->persist($cart);
        $this->entityManager->flush();
        // Persist in session
        $this->cartSessionStorage->setCart($cart);
    }
}
Enter fullscreen mode Exit fullscreen mode

With this approach, you will be able to associate a cart with a database user if you want to have a cart for logged in users. It is also a good way to let the user manage their cart on different devices and create an abandoned cart workflow.

Now that we can save carts and retrieve the current cart of the visitor, we are ready to add products to the cart.

Top comments (9)

Collapse
 
antoine__ profile image
AntoineChatry

I have this error "argument "$session" of method "__construct()" references interface "Symfony\Component\HttpFoundation\Session\SessionInterface" but no such service exists. "
And i don't really know how to fix any idea ?

Collapse
 
qferrer profile image
Quentin Ferrer

Hello. Could you check if the session is enabled in the configuration file config/packages/framework.yaml ? You should have :

session:
        # enables the support of sessions in the app
        enabled: true
Enter fullscreen mode Exit fullscreen mode

More details on symfony.com/doc/current/session.ht...

Collapse
 
antoine__ profile image
AntoineChatry

Done! but still this message

Cannot autowire service "App\Storage\CartSessionStorage": argument "$session" of method "__construct()" references interface "Symfony\Component\HttpFoundation\Session\SessionInterface" but no such service exists. Did you create a class that implements this interface?

my framework.yaml

session:
        enabled: true
        handler_id: null
        cookie_secure: auto
        cookie_samesite: lax
        storage_factory_id: session.storage.factory.native 
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
qferrer profile image
Quentin Ferrer

What version of Symfony are you using?

Thread Thread
 
antoine__ profile image
AntoineChatry

Symfony 6.0

Thread Thread
 
qferrer profile image
Quentin Ferrer • Edited

This tutorial was written with Symfony 5 and it's not compatible with version 6 at this time. In Symfony 6, the Session service has been removed. To get the Session, you now need to inject the RequestStack service and use the new getSession() method.

You need to upgrade the CartSessionStorage like that :

namespace App\Storage;

use App\Entity\Order;
use App\Repository\OrderRepository;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Class CartSessionStorage
 * @package App\Storage
 */
class CartSessionStorage
{
    /**
     * @var RequestStack
     */
    private $requestStack;

    /**
     * The cart repository.
     *
     * @var OrderRepository
     */
    private $cartRepository;

    /**
     * @var string
     */
    const CART_KEY_NAME = 'cart_id';

    /**
     * CartSessionStorage constructor.
     *
     * @param RequestStack $requestStack
     * @param OrderRepository $cartRepository
     */
    public function __construct(RequestStack $requestStack, OrderRepository $cartRepository)
    {
        $this->requestStack = $requestStack;
        $this->cartRepository = $cartRepository;
    }

    /**
     * Gets the cart in session.
     *
     * @return Order|null
     */
    public function getCart(): ?Order
    {
        return $this->cartRepository->findOneBy([
            'id' => $this->getCartId(),
            'status' => Order::STATUS_CART
        ]);
    }

    /**
     * Sets the cart in session.
     *
     * @param Order $cart
     */
    public function setCart(Order $cart): void
    {
        $this->requestStack->getSession()->set(self::CART_KEY_NAME, $cart->getId());
    }

    /**
     * Returns the cart id.
     *
     * @return int|null
     */
    private function getCartId(): ?int
    {
        return $this->requestStack->getSession()->get(self::CART_KEY_NAME);
    }
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
antoine__ profile image
AntoineChatry

It worked! It seems that everything is good now.
Thanks!

Collapse
 
soufian_69 profile image
Soufian

Try this
PHP bin/console req http

Collapse
 
butachan profile image
butachan

Hi thanks for this great tutorial,
I am taking it as reference for my project. But I have some issues (probably beginner issues)
In my project, the user is required to build a cart, so I just use UserRepository as CartSessionStorage. Is that fine ?

Secondly, I don't know how Forms pass data. When I submit addtocart, I get "too few arguments to function entity OrderItem::__construct(), 0 passed ... at least 2 expected".
this error occurs before entering in the controller method. The arguments needed are Order from the current connected user and the current item. Maybe you have an idea. As I am beginner, I don't understand how forms work