Managing the Cart Storage | Building a Shopping Cart with Symfony
Tags: php, symfony, tutorial, session
Published: 2020-12-21 12:54:53

- [Creating the Session Storage](#session-storage)
- [Creating the Cart Manager](#cart-manager)
- [Retrieving the current Cart](#current-cart)
- [Saving the Cart](#saving-cart)

----

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 <a name="session-storage"></a>
-----------------------------------------
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
<?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();
    }
}
```

Creating the Cart Manager <a name="cart-manager"></a>
--------------------------------------
Create the `CartManager` class that helps us retrieve the current cart of a user and saving the cart.  

```php
<?php

namespace App\Manager;

/**
 * Class CartManager
 * @package App\Manager
 */
class CartManager
{
}
```

Retrieving the current cart <a name="current-cart"></a>
---------------------------
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
<?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;
    }
}

```

Saving the Cart  <a name="saving-cart"></a>
---------------------------
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
<?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);
    }
}
```

*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.