*/ final class Basket implements Entity { /** * @var array */ private array $items = []; /** * @param array $items */ public function __construct( public readonly BasketId $id, array $items = [] ) { foreach ($items as $item) { if (! $item instanceof BasketProduct) { throw new \DomainException('All items shall be of type BasketProduct'); } $this->items[(string) $item->id->value] = $item; } } #[\Override] public function id(): BasketId { return $this->id; } public function has(ProductId $itemId): bool { return array_key_exists((string) $itemId->value, $this->items); } public function addOrUpdate(BasketProduct $item): void { if ($this->has($item->id)) { $existingItem = $this->items[(string) $item->id->value]; $updatedItem = $this->cloneWithQuantity( $existingItem, $existingItem->qty + $item->qty ); $this->items[(string) $item->id->value] = $updatedItem; return; } $this->items[(string) $item->id->value] = $item; } public function remove(ProductId $itemId, int $qty): void { if ($qty < 1) { throw new \DomainException('Cannot remove quantity less than 1'); } if (! $this->has($itemId)) { throw new \DomainException('Basket item does not exists'); } $existingItem = $this->items[(string) $itemId->value]; if ($existingItem->qty <= $qty) { $this->removeLine($itemId); return; } $updatedItem = $this->cloneWithQuantity( $existingItem, $existingItem->qty - $qty ); $this->items[(string) $itemId->value] = $updatedItem; } public function removeLine(ProductId $itemId): void { if (! $this->has($itemId)) { throw new \DomainException('Basket item does not exists'); } unset($this->items[(string) $itemId->value]); } /** * @return array */ public function items(): array { return $this->items; } public function totalTaxIncluded(): Money { return array_reduce( $this->items, fn (Money $total, BasketProduct $item) => $total->add($item->totalTtc()), Money::EUR(0) ); } public function totalVat(): Money { return array_reduce( $this->items, fn (Money $total, BasketProduct $item) => $total->add($item->taxTotal()), Money::EUR(0) ); } public function totalRaw(): Money { return array_reduce( $this->items, fn (Money $total, BasketProduct $item) => $total->add($item->subtotalHt()), Money::EUR(0) ); } public function totalDiscount(): Money { return array_reduce( $this->items, fn (Money $total, BasketProduct $item) => $total->add($item->discountTotal()), Money::EUR(0) ); } public function totalWeight(): int { return (int) array_sum(array_map(fn (BasketProduct $item) => $item->weightTotal(), $this->items)); } public function countItems(): int { return (int) array_sum(array_map(fn (BasketProduct $item) => $item->qty, $this->items)); } private function cloneWithQuantity(BasketProduct $item, int $qty): BasketProduct { return new BasketProduct( $item->id, $item->name, $qty, $item->unitPriceHt, $item->unitDiscount, $item->taxRate, $item->weightGrams, $item->brand, $item->incoId, $item->orderUnit, $item->slug, $item->picture, $item->description, $item->weightVariable, $item->discountRate, $item->priceKgTtc, [...$item->options] ); } }