<?php
namespace Boldr\Shop\ShopBundle;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\RequestStack;
use Doctrine\ORM\EntityManagerInterface;
use Boldr\Shop\ShopBundle\Entity\{ Customer, Order };
use Psr\Container\ContainerInterface;
use Boldr\Shop\ShopBundle\OrderFlow\OrderFlowManagerInterface;
class ShopContext implements ServiceSubscriberInterface
{
const GUEST_CUSTOMER_ID_SESSION = 'boldr_shop_guest_customer_id';
const CART_ORDER_ID_SESSION = 'cart_order_id';
public static function getSubscribedServices(): array
{
return [
'request_stack' => RequestStack::class,
'security' => Security::class,
'entity_manager' => EntityManagerInterface::class,
'order_flow_manager' => OrderFlowManagerInterface::class
];
}
private ContainerInterface $container;
/** @var Customer|null Cached customer object */
private ?Customer $customer = null;
private string $defaultCurrency;
private array $currencies;
private ?Order $currentOrder = null;
public function __construct(ContainerInterface $container, string $defaultCurrency, array $currencies)
{
$this->container = $container;
$this->defaultCurrency = $defaultCurrency;
$this->currencies = $currencies;
}
public function getSupportedCurrencies(): array
{
return $this->currencies;
}
public function getCurrency(): string
{
return $this->getCustomerCurrency($this->getCustomer());
}
private function getCustomerCurrency(?Customer $customer): string
{
$currency = $this->defaultCurrency;
// If the Customer has a preferred currency set, verify it can be used.
$preferredCurrency = $customer === null ? null : $customer->getPreferredCurrency();
if ($preferredCurrency !== null)
{
// If the customer's preferred currency is available, use it for the order
if (isset($this->currencies[$preferredCurrency]))
{
$currency = $preferredCurrency;
}
// Currency not available anymore, restore currency to default currency
else
{
$customer->setPreferredCurrency($currency);
}
}
return $currency;
}
private function createOrder(Customer $customer, string $state)
{
$currency = $this->getCurrency();
$order = new Order($customer, $state, $currency);
$customer->getOrders()->add($order);
return $order;
}
public function getCurrentOrder(): Order
{
if (!isset($this->currentOrder))
{
$this->currentOrder = $this->_getCurrentOrder();
}
return $this->currentOrder;
}
public function _getCurrentOrder(): Order
{
$cartState = $this->container->get('order_flow_manager')->getCartState();
// Get (or create) customer object
$customer = $this->getCustomer(true);
$session = $this->container->get('request_stack')->getSession();
// If carts are stored in the session only
// @TODO Make global option to share cart across the Customer
if ($customer->getSeparateCartPerSession())
{
$em = $this->container->get('entity_manager');
$session = $this->container->get('request_stack')->getSession();
// Find the cart order if there is one in the session and it belongs to the customer
$cartOrderId = $session->get(self::CART_ORDER_ID_SESSION, null);
if ($cartOrderId !== null)
{
$orderRepository = $em->getRepository(Order::class);
$cartOrder = $orderRepository->find($cartOrderId);
if ($cartOrder !== null && $cartOrder->getCustomer() === $customer)
{
$cartOrder = $this->container->get('order_flow_manager')->getCurrentOrder($customer, [$cartOrder]);
if ($cartOrder !== null)
return $cartOrder;
}
}
// If no cart was stored in the session, create a new cart and store it in the session
$cartOrder = $this->createOrder($customer, $cartState);
$em->flush();
$session->set(self::CART_ORDER_ID_SESSION, $cartOrder->getId());
return $cartOrder;
}
else
{
$orders = null;
if ($session->has(self::CART_ORDER_ID_SESSION))
{
$cartOrderId = $session->get(self::CART_ORDER_ID_SESSION);
$cartOrder = $customer->getOrders()->filter(fn($order) => $order->getId() == $cartOrderId)->first();
if ($cartOrder !== false)
{
$orders = [$cartOrder];
}
}
// If user is logged in as guest, only access current cart. If none selected, create.
if ($session->has(self::GUEST_CUSTOMER_ID_SESSION))
{
$cartOrder = null;
if ($orders !== null)
{
$cartOrder = $this->container->get('order_flow_manager')->getCurrentOrder($customer, $orders);
}
if ($cartOrder === null)
{
$cartOrder = $this->createOrder($customer, $cartState);
$this->container->get('entity_manager')->flush();
$session->set(self::CART_ORDER_ID_SESSION, $cartOrder->getId());
return $cartOrder;
}
}
// Find Order with cart state
$cartOrder = $this->container->get('order_flow_manager')->getCurrentOrder($customer, $orders);
// Create a new order if no cart order exists yet
if ($cartOrder === null)
{
if ($session->has(self::CART_ORDER_ID_SESSION))
{
$session->remove(self::CART_ORDER_ID_SESSION);
$cartOrder = $this->container->get('order_flow_manager')->getCurrentOrder($customer);
}
if ($cartOrder === null)
{
$cartOrder = $this->createOrder($customer, $cartState);
}
}
return $cartOrder;
}
}
public function getCustomer(bool $createIfNotExists = false): ?Customer
{
if ($this->customer !== null)
{
return $this->customer;
}
$em = $this->container->get('entity_manager');
$repoCustomers = $em->getRepository(Customer::class);
$session = $this->container->get('request_stack')->getSession();
// 1. Check for user customer
$user = $this->container->get('security')->getUser();
if ($user !== null)
{
$customer = $repoCustomers->createQueryBuilder('c')
->leftJoin('c.customerUsers', 'cu')
->leftJoin('cu.user', 'u')
->where('u = ?1')
->setParameter(1, $user)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if ($customer !== null)
{
if ($session->has(self::GUEST_CUSTOMER_ID_SESSION))
{
$id = $session->get(self::GUEST_CUSTOMER_ID_SESSION);
$guestCustomer = $repoCustomers->find($id);
if ($guestCustomer !== null)
{
// Transfer current order to other customer
$currentOrder = $this->container->get('order_flow_manager')->getCurrentOrder($guestCustomer);
if ($currentOrder !== null)
{
$currentOrder->setCustomer($customer);
$customer->getOrders()->add($currentOrder);
$session->set(self::CART_ORDER_ID_SESSION, $currentOrder->getId());
}
$em->flush();
}
}
$this->customer = $customer;
return $customer;
}
}
// 2. Check for guest customer
if ($session->has(self::GUEST_CUSTOMER_ID_SESSION))
{
$id = $session->get(self::GUEST_CUSTOMER_ID_SESSION);
$customer = $repoCustomers->find($id);
if ($customer !== null)
{
if ($customer->getUsers()->count() !== 0)
{
$session->remove(self::GUEST_CUSTOMER_ID_SESSION);
}
else
{
if ($customer !== null)
{
// 2.1. Attach guest customer to user if logged in
if ($user !== null)
{
$customer->addUser($user);
$em->flush($customer);
}
$this->customer = $customer;
return $customer;
}
}
}
}
// If a customer is not really necessary, just return null
if (!$createIfNotExists)
{
return null;
}
// If a customer object is neccessary for data persistence, e.g. when adding products to the cart
// create a new customer object and store it in the session
// 3. Create a new customer
$session = $this->container->get('request_stack')->getSession();
$locale = $this->container->get('request_stack')->getCurrentRequest()->getLocale();
$newCustomer = new Customer();
$newCustomer->setLocale($locale);
$em->persist($newCustomer);
$this->customer = $newCustomer;
// 3.1. Attach the customer to the user if logged in
if ($user !== null)
{
$newCustomer->addUser($user);
$em->flush($newCustomer);
return $newCustomer;
}
// 3.2. Store the new guest customer in the session
// Obtain an ID
$em->flush($newCustomer);
$id = $newCustomer->getId();
$session->set(self::GUEST_CUSTOMER_ID_SESSION, $id);
return $newCustomer;
}
public function setGuestCustomer(Customer $customer): void
{
$session = $this->container->get('request_stack')->getSession();
$session->set(self::GUEST_CUSTOMER_ID_SESSION, $customer->getId());
}
}