0Pricing
PHP Academy · Lekcja

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

  1. Elementy DDD: encje i obiekty wartości
  2. Agregaty, repozytoria i fabryki
  3. Zdarzenia domenowe i usługi domenowe
  4. Konteksty ograniczone i mapowanie kontekstów
← Powrót do PHP Academy