ldc-boutique-cnc-sable/infra/Adapters/Serializers/BasketSerializer.php

149 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Infrastructure\Adapters\Serializers;
use Domain\Enums\ProductBrand;
use Domain\Enums\TaxRate;
use Domain\Enums\Units\OrderUnit;
use Domain\Models\Entities\Basket;
use Domain\Models\ValueObjects\BasketProduct;
use Domain\Models\ValueObjects\DiscountRate;
use Domain\Models\ValueObjects\Entities\BasketId;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\IncoId;
use Illuminate\Support\Arr;
use Money\Currency;
use Money\Money;
use Ramsey\Uuid\Uuid;
class BasketSerializer
{
/** @return array<string,mixed> */
public function toArray(Basket $basket): array
{
return [
'id' => (string) $basket->id->value,
'items' => array_map(
fn (BasketProduct $item) => [
'id' => (string) $item->id->value,
'name' => $item->name,
'qty' => $item->qty,
'unitPriceHt' => $item->unitPriceHt,
'unitDiscount' => $item->unitDiscount,
'taxRate' => $item->taxRate->value,
'weightGrams' => $item->weightGrams,
'brand' => $item->brand?->value,
'incoId' => $item->incoId?->value,
'orderUnit' => $item->orderUnit->value,
'slug' => $item->slug,
'picture' => $item->picture,
'description' => $item->description,
'weightVariable' => $item->weightVariable,
'discountRateBp' => $item->discountRate->basisPoints(),
'priceKgTtc' => $item->priceKgTtc,
'options' => $item->options,
],
array_values($basket->items())
),
];
}
/** @param array<string,mixed> $data */
public function fromArray(array $data): Basket
{
$basketId = Arr::string($data, 'id');
$rawItems = Arr::array($data, 'items', []);
$hydratedItems = [];
foreach ($rawItems as $itemData) {
$itemData = Arr::wrap($itemData);
$id = Arr::string($itemData, 'id');
$name = Arr::string($itemData, 'name');
$qty = Arr::integer($itemData, 'qty');
$unitPriceHt = Arr::array($itemData, 'unitPriceHt');
$unitPriceHtCurrency = Arr::string($unitPriceHt, 'currency');
$unitDiscount = Arr::array($itemData, 'unitDiscount');
$unitDiscountCurrency = Arr::string($unitDiscount, 'currency');
$taxRate = Arr::integer($itemData, 'taxRate');
$weightGrams = Arr::integer($itemData, 'weightGrams');
$brand = Arr::get($itemData, 'brand'); // using get because it can be null
$incoId = Arr::get($itemData, 'incoId');
$orderUnit = Arr::string($itemData, 'orderUnit');
$slug = Arr::string($itemData, 'slug');
$picture = Arr::get($itemData, 'picture');
$description = Arr::get($itemData, 'description');
$weightVariable = Arr::boolean($itemData, 'weightVariable');
$discountRate = $this->hydrateDiscountRate($itemData);
$priceKgTtc = Arr::array($itemData, 'priceKgTtc');
$priceKgTtcCurrency = Arr::string($priceKgTtc, 'currency');
$options = Arr::array($itemData, 'options', []);
if (empty($unitPriceHtCurrency)) {
throw new \DomainException("Currency is required for unitPriceHt of item with id $id");
}
if (empty($unitDiscountCurrency)) {
throw new \DomainException("Currency is required for unitDiscount of item with id $id");
}
if (empty($priceKgTtcCurrency)) {
throw new \DomainException("Currency is required for priceKgTtc of item with id $id");
}
$hydratedItems[$id] = new BasketProduct(
id: new ProductId(Uuid::fromString($id)),
name: $name,
qty: $qty,
unitPriceHt: new Money(
is_scalar($a = Arr::get($unitPriceHt, 'amount', 0)) ? (int) $a : 0,
new Currency($unitPriceHtCurrency)
),
unitDiscount: new Money(
is_scalar($a = Arr::get($unitDiscount, 'amount', 0)) ? (int) $a : 0,
new Currency($unitDiscountCurrency)
),
taxRate: TaxRate::from($taxRate),
weightGrams: $weightGrams,
brand: is_string($brand) && $brand !== '' ? ProductBrand::from($brand) : null,
incoId: is_scalar($incoId) && $incoId !== 0 ? new IncoId((int) $incoId) : null,
orderUnit: OrderUnit::from($orderUnit),
slug: $slug,
picture: is_string($picture) && $picture !== '' ? $picture : null,
description: is_string($description) && $description !== '' ? $description : null,
weightVariable: $weightVariable,
discountRate: $discountRate,
priceKgTtc: new Money(
is_scalar($a = Arr::get($priceKgTtc, 'amount', 0)) ? (int) $a : 0,
new Currency($priceKgTtcCurrency)
),
options: $options
);
}
return new Basket(
id: new BasketId(Uuid::fromString($basketId)),
items: $hydratedItems
);
}
/**
* Reads the discount rate in basis points; falls back to the legacy float ratio
* (key "discountRate") stored in sessions created before the DiscountRate value object.
*
* @param array<array-key,mixed> $itemData
*/
private function hydrateDiscountRate(array $itemData): DiscountRate
{
$basisPoints = Arr::get($itemData, 'discountRateBp');
if (is_int($basisPoints)) {
return DiscountRate::fromBasisPoints($basisPoints);
}
$legacyRatio = Arr::get($itemData, 'discountRate', 0.0);
return is_numeric($legacyRatio)
? DiscountRate::fromRatio(min(max((float) $legacyRatio, 0.0), 1.0))
: DiscountRate::zero();
}
}