<?php
declare(strict_types=1);
namespace App\Controller\Blo;
use App\Entity\Blo\Cart;
use App\Entity\Blo\CartItem;
use App\Repository\Blo\CartRepository;
use App\Repository\Blo\ProductRepository;
use App\Repository\Blo\ProductVariantRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/cart')]
class CartController extends AbstractController
{
public function __construct(
private readonly CartRepository $cartRepository,
private readonly ProductRepository $productRepository,
private readonly ProductVariantRepository $productVariantRepository,
private readonly EntityManagerInterface $em,
private readonly RequestStack $requestStack,
) {
}
#[Route('', name: 'blo_cart', methods: ['GET'])]
public function index(): Response
{
$cart = $this->getOrCreateCart();
return $this->render('blo/cart/index.html.twig', [
'cart' => $cart,
]);
}
#[Route('/add/{productId}', name: 'blo_cart_add', methods: ['POST'])]
public function add(int $productId, Request $request): Response
{
$product = $this->productRepository->find($productId);
if (!$product || $product->getStatus() !== 'APPROVED') {
$this->addFlash('error', 'Produit introuvable ou indisponible.');
return $this->redirectToRoute('blo_catalog');
}
$variantId = $request->request->getInt('variant_id');
$variant = null;
if ($variantId) {
$variant = $this->productVariantRepository->find($variantId);
if (!$variant || $variant->getProduct()->getId() !== $product->getId()) {
$this->addFlash('error', 'Variante invalide.');
return $this->redirectToRoute('blo_product_show', ['slug' => $product->getSlug()]);
}
} elseif ($product->hasVariants()) {
$this->addFlash('error', 'Veuillez sélectionner une taille et une couleur.');
return $this->redirectToRoute('blo_product_show', ['slug' => $product->getSlug()]);
}
$maxStock = $variant ? $variant->getStockQty() : $product->getStockQty();
$qty = max(1, (int) $request->request->get('qty', 1));
if ($qty > $maxStock) {
$this->addFlash('error', 'Stock insuffisant.');
return $this->redirectToRoute('blo_product_show', ['slug' => $product->getSlug()]);
}
$cart = $this->getOrCreateCart();
$existing = null;
foreach ($cart->getItems() as $item) {
if ($item->getProduct()->getId() !== $productId) {
continue;
}
$itemVariantId = $item->getProductVariant()?->getId();
if (($variant?->getId() ?? 0) === ($itemVariantId ?? 0)) {
$existing = $item;
break;
}
}
if ($existing) {
$newQty = $existing->getQty() + $qty;
$limit = $existing->getProductVariant() ? $existing->getProductVariant()->getStockQty() : $existing->getProduct()->getStockQty();
if ($newQty > $limit) {
$this->addFlash('error', 'Stock insuffisant.');
return $this->redirectToRoute('blo_cart');
}
$existing->setQty($newQty);
} else {
$item = new CartItem();
$item->setCart($cart);
$item->setProduct($product);
$item->setProductVariant($variant);
$item->setQty($qty);
$item->setPriceSnapshot($product->getEffectivePrice());
$cart->addItem($item);
}
$cart->setUpdatedAt(new \DateTimeImmutable());
$this->em->flush();
$this->addFlash('success', 'Produit ajouté au panier.');
if ($request->request->get('action') === 'buy_now') {
return $this->redirectToRoute('blo_checkout');
}
return $this->redirect($request->headers->get('Referer', $this->generateUrl('blo_catalog')));
}
#[Route('/update/{itemId}', name: 'blo_cart_update', methods: ['POST'])]
public function update(int $itemId, Request $request): Response
{
if (!$this->isCsrfTokenValid('blo_cart_update_' . $itemId, $request->request->get('_csrf_token'))) {
$this->addFlash('error', 'Jeton de sécurité invalide.');
return $this->redirectToRoute('blo_cart');
}
$cart = $this->getOrCreateCart();
$item = null;
foreach ($cart->getItems() as $i) {
if ($i->getId() === $itemId) {
$item = $i;
break;
}
}
if (!$item) {
return $this->redirectToRoute('blo_cart');
}
$qty = max(0, (int) $request->request->get('qty', 0));
if ($qty === 0) {
$cart->removeItem($item);
$this->em->remove($item);
} else {
$maxStock = $item->getProductVariant() ? $item->getProductVariant()->getStockQty() : $item->getProduct()->getStockQty();
if ($qty > $maxStock) {
$this->addFlash('error', 'Stock insuffisant.');
return $this->redirectToRoute('blo_cart');
}
$item->setQty($qty);
}
$cart->setUpdatedAt(new \DateTimeImmutable());
$this->em->flush();
return $this->redirectToRoute('blo_cart');
}
#[Route('/remove/{itemId}', name: 'blo_cart_remove', methods: ['POST'])]
public function remove(int $itemId, Request $request): Response
{
if (!$this->isCsrfTokenValid('blo_cart_remove_' . $itemId, $request->request->get('_csrf_token'))) {
$this->addFlash('error', 'Jeton de sécurité invalide.');
return $this->redirectToRoute('blo_cart');
}
$cart = $this->getOrCreateCart();
foreach ($cart->getItems() as $item) {
if ($item->getId() === $itemId) {
$cart->removeItem($item);
$this->em->remove($item);
break;
}
}
$cart->setUpdatedAt(new \DateTimeImmutable());
$this->em->flush();
return $this->redirectToRoute('blo_cart');
}
private function getOrCreateCart(): Cart
{
$user = $this->getUser();
$sessionId = $this->requestStack->getSession()->getId();
if ($user) {
$cart = $this->cartRepository->findByCustomer($user);
if (!$cart) {
$cart = $this->cartRepository->findBySessionId($sessionId);
if ($cart) {
$cart->setCustomer($user);
$cart->setSessionId(null);
$this->em->flush();
}
}
} else {
$cart = $this->cartRepository->findBySessionId($sessionId);
}
if (!$cart) {
$cart = new Cart();
$cart->setCustomer($user);
if (!$user) {
$cart->setSessionId($sessionId);
}
$this->em->persist($cart);
$this->em->flush();
}
return $cart;
}
}