feat(cart): add session cart service
This commit is contained in:
1 parent
5853881f30
commit
170c24ee52
1 file changed
+175
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Bazaar\Services;
|
||||
|
||||
use Bazaar\Core\Session;
|
||||
use Bazaar\Repositories\ProductRepository;
|
||||
use Bazaar\Repositories\VariantRepository;
|
||||
|
||||
final class Cart
|
||||
{
|
||||
private const KEY = 'cart';
|
||||
|
||||
public function __construct(
|
||||
private readonly Session $session,
|
||||
private readonly ProductRepository $products,
|
||||
private readonly ?VariantRepository $variants = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function add(int $productId, int $quantity, ?int $variantId = null): void
|
||||
{
|
||||
if ($quantity < 1) {
|
||||
return;
|
||||
}
|
||||
$resolved = $this->resolve($productId, $variantId);
|
||||
if ($resolved === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = $this->raw();
|
||||
$key = $this->lineKey($productId, $variantId);
|
||||
$desired = ($lines[$key]['quantity'] ?? 0) + $quantity;
|
||||
$quantity = min($desired, $resolved['availableStock']);
|
||||
if ($quantity < 1) {
|
||||
unset($lines[$key]);
|
||||
} else {
|
||||
$lines[$key] = ['productId' => $productId, 'variantId' => $variantId, 'quantity' => $quantity];
|
||||
}
|
||||
$this->persist($lines);
|
||||
}
|
||||
|
||||
public function update(int $productId, int $quantity, ?int $variantId = null): void
|
||||
{
|
||||
$lines = $this->raw();
|
||||
$key = $this->lineKey($productId, $variantId);
|
||||
if ($quantity < 1) {
|
||||
unset($lines[$key]);
|
||||
$this->persist($lines);
|
||||
return;
|
||||
}
|
||||
$resolved = $this->resolve($productId, $variantId);
|
||||
if ($resolved === null) {
|
||||
unset($lines[$key]);
|
||||
$this->persist($lines);
|
||||
return;
|
||||
}
|
||||
$lines[$key] = [
|
||||
'productId' => $productId,
|
||||
'variantId' => $variantId,
|
||||
'quantity' => min($quantity, $resolved['availableStock']),
|
||||
];
|
||||
$this->persist($lines);
|
||||
}
|
||||
|
||||
public function remove(int $productId, ?int $variantId = null): void
|
||||
{
|
||||
$lines = $this->raw();
|
||||
unset($lines[$this->lineKey($productId, $variantId)]);
|
||||
$this->persist($lines);
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->session->forget(self::KEY);
|
||||
}
|
||||
|
||||
public function raw(): array
|
||||
{
|
||||
$lines = $this->session->get(self::KEY, []);
|
||||
return is_array($lines) ? $lines : [];
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->raw() === [];
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($this->raw() as $line) {
|
||||
$total += (int) $line['quantity'];
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
public function lines(): array
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($this->raw() as $line) {
|
||||
$resolved = $this->resolve((int) $line['productId'], $line['variantId']);
|
||||
if ($resolved === null) {
|
||||
continue;
|
||||
}
|
||||
$quantity = (int) $line['quantity'];
|
||||
$lines[] = [
|
||||
'productId' => (int) $line['productId'],
|
||||
'variantId' => $line['variantId'],
|
||||
'product' => $resolved['product'],
|
||||
'variant' => $resolved['variant'],
|
||||
'name' => $resolved['product']['name'],
|
||||
'variantLabel' => $resolved['variantLabel'],
|
||||
'unitPriceCents' => $resolved['unitPriceCents'],
|
||||
'availableStock' => $resolved['availableStock'],
|
||||
'quantity' => $quantity,
|
||||
'lineTotalCents' => $resolved['unitPriceCents'] * $quantity,
|
||||
];
|
||||
}
|
||||
return $lines;
|
||||
}
|
||||
|
||||
public function totalCents(): int
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($this->lines() as $line) {
|
||||
$total += $line['lineTotalCents'];
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function resolve(int $productId, ?int $variantId): ?array
|
||||
{
|
||||
$product = $this->products->findActive($productId);
|
||||
if ($product === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($variantId !== null && $this->variants !== null) {
|
||||
$variant = $this->variants->findActive($variantId);
|
||||
if ($variant === null || (int) $variant['productId'] !== $productId) {
|
||||
return null;
|
||||
}
|
||||
$options = $this->variants->options($variantId);
|
||||
return [
|
||||
'product' => $product,
|
||||
'variant' => $variant,
|
||||
'variantLabel' => $this->variants->label($options),
|
||||
'unitPriceCents' => (int) $variant['priceCents'],
|
||||
'availableStock' => (int) $variant['stockQuantity'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'product' => $product,
|
||||
'variant' => null,
|
||||
'variantLabel' => '',
|
||||
'unitPriceCents' => (int) $product['priceCents'],
|
||||
'availableStock' => (int) $product['stockQuantity'],
|
||||
];
|
||||
}
|
||||
|
||||
private function lineKey(int $productId, ?int $variantId): string
|
||||
{
|
||||
return $variantId !== null ? 'v' . $variantId : 'p' . $productId;
|
||||
}
|
||||
|
||||
private function persist(array $lines): void
|
||||
{
|
||||
if ($lines === []) {
|
||||
$this->session->forget(self::KEY);
|
||||
return;
|
||||
}
|
||||
$this->session->put(self::KEY, $lines);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user