Elementy DDD: encje i obiekty wartości
Proszę modelować domenę za pomocą rozbudowanych encji i niezmiennych obiektów wartości.
Elementy DDD: encje i obiekty wartości to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Rdzeń taktyczny
Wzorce taktyczne modelowania domeny (DDD) dostarczają słownictwa do odwzorowywania pojęć biznesowych w kodzie. Dwa fundamentalne elementy budulcowe to Encje (obiekty definiowane przez tożsamość zachowywaną w czasie) oraz Obiekty wartościowe (obiekty definiowane przez swoje atrybuty i traktowane jako niezmienne). Właściwe rozróżnienie tych pojęć kształtuje całą pozostałą warstwę domenową.
Tożsamość a wartość
Encja ma stabilną tożsamość, która pozostaje taka sama nawet po zmianie jej atrybutów: Customer jest tym samym klientem, nawet gdy zmieni miejsce zamieszkania. Obiekt wartości nie ma tożsamości; dwa obiekty wartości o takich samych atrybutach można stosować zamiennie, podobnie jak dwa banknoty 5-dolarowe. Proszę zadać sobie pytanie: „jeśli zmienią się wszystkie pola, czy to nadal ta sama rzecz?” Jeśli tak, jest to encja.
Obiekt wartości
Obiekty wartości są niezmienne i samowalidujące. Właściwości readonly w PHP 8.1 wymuszają niezmienność, a konstruktor chroni niezmienniki, dzięki czemu nie może istnieć nieprawidłowa instancja.
<?php
final class Email {
public function __construct(public readonly string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email: $value");
}
}
public function equals(Email $other): bool {
return strtolower($this->value) === strtolower($other->value);
}
}
$a = new Email('Jane@Example.com');
var_dump($a->equals(new Email('jane@example.com'))); // true
Równość według wartości
Obiekty wartości porównuje się według równości strukturalnej, a nie referencji. Należy udostępnić jawną metodę equals() zamiast polegać na operatorze ==, ponieważ luźne porównanie w PHP ma zaskakujące zasady. Obiekt wartości Money musi uwzględniać zarówno kwotę, jak i walutę.
<?php
final class Money {
public function __construct(
public readonly int $cents,
public readonly string $currency
) {}
public function equals(Money $o): bool {
return $this->cents === $o->cents && $this->currency === $o->currency;
}
public function add(Money $o): self {
if ($this->currency !== $o->currency) {
throw new DomainException('Currency mismatch');
}
return new self($this->cents + $o->cents, $this->currency);
}
}
$sum = (new Money(500, 'USD'))->add(new Money(250, 'USD'));
echo $sum->cents, PHP_EOL; // 750
Niezmienność i metody „with”
Ponieważ obiekty wartości są niezmienne, „zmiana” jednego oznacza zwrócenie nowej instancji. Metody takie jak add() czy withAmount() nigdy nie modyfikują obiektu; tworzą nową wartość. Eliminuje to błędy aliasowania i sprawia, że obiekty wartości można bezpiecznie współdzielić w całej domenie.
<?php
final class DateRange {
public function __construct(
public readonly DateTimeImmutable $start,
public readonly DateTimeImmutable $end
) {
if ($end < $start) { throw new InvalidArgumentException('end before start'); }
}
public function withEnd(DateTimeImmutable $end): self {
return new self($this->start, $end);
}
}
$r = new DateRange(new DateTimeImmutable('2026-01-01'), new DateTimeImmutable('2026-01-10'));
$r2 = $r->withEnd(new DateTimeImmutable('2026-02-01'));
echo $r->end->format('Y-m-d'), ' / ', $r2->end->format('Y-m-d'), PHP_EOL;
Encja
Encję definiuje tożsamość, zazwyczaj identyfikator wygenerowany w domenie, a nie autoinkrementacja bazy danych. Równość porównuje identyfikatory. Jej atrybuty mogą się zmieniać za pośrednictwem metod zawierających logikę, które chronią niezmienniki.
<?php
final class Customer {
private Email $email;
public function __construct(
public readonly string $id,
Email $email
) { $this->email = $email; }
public function changeEmail(Email $new): void { $this->email = $new; }
public function email(): Email { return $this->email; }
public function sameIdentityAs(Customer $o): bool { return $this->id === $o->id; }
}
$c = new Customer('cus_1', new Email('a@b.com'));
$c->changeEmail(new Email('c@d.com'));
echo $c->email()->value, PHP_EOL; // c@d.com
Generowanie tożsamości
Zaleca się generowanie tożsamości w domenie (np. UUID) przed utrwaleniem danych, zamiast czekania na bazę danych. Dzięki temu można utworzyć w pamięci w pełni poprawną encję, odwoływać się do niej w różnych agregatach i testować bez bazy danych. Identyfikator jest częścią modelu, a nie artefaktem warstwy przechowywania.
Bogate zachowanie, nie anemiczne dane
Częstym antywzorcem jest anemiczny model domenowy: encje są zbiorami publicznych getterów i setterów, podczas gdy cała logika znajduje się w klasach „serwisowych”. DDD przenosi zachowanie do encji. $order->cancel() hermetyzuje reguły anulowania zamiast pozwalać wywołującym bezpośrednio zmieniać pole statusu.
<?php
final class Order {
private string $status = 'open';
public function __construct(public readonly string $id) {}
public function cancel(): void {
if ($this->status === 'shipped') {
throw new DomainException('Cannot cancel a shipped order');
}
$this->status = 'cancelled';
}
public function status(): string { return $this->status; }
}
$o = new Order('o1');
$o->cancel();
echo $o->status(), PHP_EOL; // cancelled
Obiekty wartości w całym modelu
Zastąp obsesję typów prostych obiektami wartości. Zamiast przekazywać w różnych miejscach string $email, int $cents i string $currency, opakuj je w Email i Money. Korzyści:
- Walidacja odbywa się raz, podczas tworzenia.
- Reguły domenowe (zgodność walut) znajdują się razem z danymi.
- Sygnatury typów dokumentują intencję i zapobiegają pomyłkom.
Uwaga dotycząca mapowania trwałości
Obiekty wartości często mapuje się na osadzane kolumny, a nie na osobne tabele (Doctrine #[Embeddable]). Encje mapuje się na wiersze identyfikowane przez ich tożsamość. Nie należy umieszczać kwestii utrwalania danych w samych obiektach domenowych: model nie powinien wiedzieć, że jest przechowywany w MySQL. ORM lub napisany ręcznie mapper tłumaczy dane między bogatą domeną a bazą danych.
Złożone obiekty wartości
Obiekty wartości można łączyć. Address skupia kilka typów prostych w jeden spójny koncept, który ma własną równość i formatowanie. Encja przechowuje wtedy jeden bogaty typ zamiast pięciu luźnych ciągów znaków, a reguły dotyczące adresu znajdują się w jednym miejscu.
<?php
final class Address {
public function __construct(
public readonly string $street,
public readonly string $city,
public readonly string $postcode
) {
if ($postcode === '') { throw new InvalidArgumentException('postcode required'); }
}
public function equals(Address $o): bool {
return $this->street === $o->street
&& $this->city === $o->city
&& $this->postcode === $o->postcode;
}
public function oneLine(): string {
return "{$this->street}, {$this->city} {$this->postcode}";
}
}
echo (new Address('1 Main St', 'Ankara', '06000'))->oneLine(), PHP_EOL;
Szybki test
Encja czy obiekt wartości?
Podsumowanie
Poznali Państwo dwa podstawowe elementy konstrukcyjne DDD. Encje definiuje stabilna tożsamość; zawierają bogate zachowanie i chronią swoje niezmienniki za pomocą metod. Obiekty wartości są niezmienne, samowalidujące i porównywane według równości atrybutów, zastępując typy proste. Poprawne modelowanie tych elementów, z logiką umieszczoną w obiektach zamiast w anemicznych zbiorach danych, stanowi podstawę agregatów, repozytoriów i pozostałej warstwy domenowej.
Często zadawane pytania
Czy lekcja „Elementy DDD: encje i obiekty wartości” jest bezpłatna?
Tak — pełny tekst „Elementy DDD: encje i obiekty wartości” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Elementy DDD: encje i obiekty wartości”?
Proszę modelować domenę za pomocą rozbudowanych encji i niezmiennych obiektów wartości. Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Elementy DDD: encje i obiekty wartości”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Elementy DDD: encje i obiekty wartości
- Agregaty, repozytoria i fabryki
- Zdarzenia domenowe i usługi domenowe
- Konteksty ograniczone i mapowanie kontekstów