Step 00 — Squelette + intégration naïve
3 Bounded Contexts (Sales, Invoicing, LegacyFulfillment) avec : - Domaines complets (agrégats, VOs, événements, invariants) - Couche application (commands, queries, ports) - Infrastructure in-memory (repos, gateway fake) - Controllers HTTP Symfony - Couplage naïf synchrone entre BC via NaiveSalesEventPublisher - 20 tests unitaires et d'intégration passants
This commit is contained in:
115
src/Sales/Domain/Model/Order.php
Normal file
115
src/Sales/Domain/Model/Order.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MiniShop\Sales\Domain\Model;
|
||||
|
||||
use MiniShop\Sales\Domain\Event\OrderCancelled;
|
||||
use MiniShop\Sales\Domain\Event\OrderConfirmed;
|
||||
use MiniShop\Sales\Domain\Event\OrderPlaced;
|
||||
use MiniShop\Sales\Domain\Exception\EmptyOrderException;
|
||||
use MiniShop\Sales\Domain\Exception\InvalidOrderStateException;
|
||||
|
||||
final class Order
|
||||
{
|
||||
private OrderStatus $status;
|
||||
/** @var list<OrderLine> */
|
||||
private array $lines;
|
||||
/** @var list<object> */
|
||||
private array $domainEvents = [];
|
||||
|
||||
private function __construct(
|
||||
public readonly OrderId $id,
|
||||
public readonly CustomerId $customerId,
|
||||
private readonly \DateTimeImmutable $placedAt,
|
||||
) {
|
||||
$this->status = OrderStatus::Draft;
|
||||
$this->lines = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<OrderLine> $lines
|
||||
*/
|
||||
public static function place(
|
||||
OrderId $id,
|
||||
CustomerId $customerId,
|
||||
array $lines,
|
||||
\DateTimeImmutable $placedAt,
|
||||
): self {
|
||||
if ($lines === []) {
|
||||
throw EmptyOrderException::noLines();
|
||||
}
|
||||
|
||||
$order = new self($id, $customerId, $placedAt);
|
||||
$order->lines = $lines;
|
||||
$order->status = OrderStatus::Placed;
|
||||
|
||||
$order->recordEvent(new OrderPlaced($id, $customerId, $order->total(), $placedAt));
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
public function confirm(): void
|
||||
{
|
||||
if ($this->status !== OrderStatus::Placed) {
|
||||
throw InvalidOrderStateException::cannotConfirm($this->status);
|
||||
}
|
||||
|
||||
$this->status = OrderStatus::Confirmed;
|
||||
$this->recordEvent(new OrderConfirmed(
|
||||
$this->id,
|
||||
$this->customerId,
|
||||
$this->total(),
|
||||
$this->lines,
|
||||
));
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
if ($this->status !== OrderStatus::Placed) {
|
||||
throw InvalidOrderStateException::cannotCancel($this->status);
|
||||
}
|
||||
|
||||
$this->status = OrderStatus::Cancelled;
|
||||
$this->recordEvent(new OrderCancelled($this->id));
|
||||
}
|
||||
|
||||
public function total(): Money
|
||||
{
|
||||
return array_reduce(
|
||||
$this->lines,
|
||||
static fn (Money $carry, OrderLine $line): Money => $carry->add($line->lineTotal()),
|
||||
Money::zero($this->lines[0]->unitPrice->currency),
|
||||
);
|
||||
}
|
||||
|
||||
public function status(): OrderStatus
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/** @return list<OrderLine> */
|
||||
public function lines(): array
|
||||
{
|
||||
return $this->lines;
|
||||
}
|
||||
|
||||
public function placedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->placedAt;
|
||||
}
|
||||
|
||||
/** @return list<object> */
|
||||
public function releaseEvents(): array
|
||||
{
|
||||
$events = $this->domainEvents;
|
||||
$this->domainEvents = [];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
private function recordEvent(object $event): void
|
||||
{
|
||||
$this->domainEvents[] = $event;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user