
<?php
//entity
class OrderLine {
function __construct(
public readonly string $sku,
public readonly int $price,
public readonly int $count
) {
if ($count <= 0) {
throw new InvalidArgumentException("bad count");
}
}
function cost(): int {
return $this->price * $this->count;
}
}
//entity
class Order {
private array $lines = [];
private bool $paid = false;
function __construct(public readonly int $id) {}
function add(string $sku, int $price,
int $count): void {
$this->lines[] = new OrderLine($sku, $price,
$count);
}
function total(): int {
$sum = 0;
foreach ($this->lines as $line) {
$sum += $line->cost();
}
return $sum;
}
//business rule: 10% off from 200
function discount(): int {
$total = $this->total();
return $total >= 200 ? intdiv($total, 10) : 0;
}
function payable(): int {
return $this->total() - $this->discount();
}
//business rule: an empty order is never paid
function pay(): void {
if ($this->lines === []) {
throw new LogicException("empty order");
}
$this->paid = true;
}
function paid(): bool {
return $this->paid;
}
function size(): int {
return count($this->lines);
}
}
//client code
//no database and no screen anywhere above
$order = new Order(1001);
$order->add("pen", 30, 2);
$order->add("lamp", 190, 1);
echo "order ", $order->id, "\n";
echo "lines ", $order->size(), "\n";
echo "total ", $order->total(), "\n";
echo "discount ", $order->discount(), "\n";
echo "payable ", $order->payable(), "\n";
$order->pay();
//echo shows a bool as 1 and as nothing at all, so a
//bool goes through var_export
echo "paid ", var_export($order->paid(), true), "\n";
try {
(new Order(1002))->pay();
} catch (LogicException $error) {
echo "refused: ", $error->getMessage(), "\n";
}
try {
$order->add("chair", 15, 0);
} catch (InvalidArgumentException $error) {
echo "refused: ", $error->getMessage(), "\n";
}
//printed: order 1001
// lines 2
// total 250
// discount 25
// payable 225
// paid true
// refused: empty order
// refused: bad count
Сущность держит правило предметной области и ничего не знает ни о базе, ни об экране, ни о сети, поэтому её можно проверить без них.Read moreЗадача. Магазин даёт скидку 10% на любой заказ от 200. Если это правило живёт на веб-странице и ещё раз в запросе к базе данных, две копии скоро разойдутся. Чтобы проверить правило, приходится запускать страницу или базу. Самое важное знание магазина разбросано, и проверить его трудно. Идея. Сущность - это данные вместе с бизнес-правилами, которые к ним относятся: из чего состоит заказ, как считается его итог, когда его можно оплатить. О базе данных, экране или сети она ничего не знает. В чистой архитектуре есть одно главное правило - правило зависимостей: внешний код может знать внутренний, но внутренний никогда не знает внешнего. Сущности - самый внутренний код, поэтому они ни от чего не зависят. Как читать пример. Сущностей две: позиция заказа и сам заказ. Заказ складывает свои позиции, даёт скидку 10% при сумме от 200 и считает сумму к оплате. Пока заказ пуст, он отказывается принимать оплату. Клиентский код собирает заказ 1001 из двух позиций и печатает его числа: итог 250, скидка 25, к оплате 225. Ещё он пытается оплатить пустой заказ, и сущность отказывает. В некоторых языках сущность - не класс, а обычное значение с функциями; роль у неё та же. Когда применять. Сущность стоит заводить для каждого понятия бизнеса, у которого есть свои правила, например для заказа или счёта. Данным без правил сущность не нужна: хватит простой структуры. Частая ошибка. Сущность, которая сама сохраняет себя в базу данных или оформляет себя для экрана. Тогда она зависит от внешнего кода, и её правила уже нельзя проверить отдельно. |