PHP Academy · บทเรียน

องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า

จำลองโดเมนด้วยเอนทิตีที่มีพฤติกรรมครบถ้วนและออบเจ็กต์ค่าที่เปลี่ยนแปลงไม่ได้

บทเรียน 1 จาก 413 ขั้นตอน

องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

แกนเชิงยุทธวิธี

รูปแบบเชิงยุทธวิธีของการออกแบบที่ขับเคลื่อนด้วยโดเมนช่วยให้คุณมีคำศัพท์สำหรับสร้างแบบจำลองแนวคิดทางธุรกิจในโค้ด ส่วนประกอบพื้นฐานสำคัญสองอย่างคือเอนทิตี (อ็อบเจกต์ที่กำหนดด้วยอัตลักษณ์ตลอดช่วงเวลา) และอ็อบเจกต์ค่า (อ็อบเจกต์ที่กำหนดด้วยแอตทริบิวต์และถือว่าเปลี่ยนแปลงไม่ได้) การแยกแยะสองสิ่งนี้ให้ถูกต้องจะกำหนดรูปแบบของทุกสิ่งที่เหลือในชั้นโดเมน

อัตลักษณ์เทียบกับค่า

เอนทิตี มีอัตลักษณ์ที่คงที่และยังคงเดิมแม้แอตทริบิวต์จะเปลี่ยนไป: Customer ยังคงเป็นลูกค้ารายเดิมแม้จะย้ายบ้าน ถ้าเป็น อ็อบเจ็กต์ค่า จะไม่มีอัตลักษณ์ ดังนั้นอ็อบเจ็กต์ค่าสองรายการที่มีแอตทริบิวต์เท่ากันจึงใช้แทนกันได้ เช่น ธนบัตรมูลค่า 5 ดอลลาร์สองใบ ลองถามว่า "หากช่องข้อมูลทุกช่องเปลี่ยนไป สิ่งนี้ยังเป็นสิ่งเดิมอยู่หรือไม่?" หากคำตอบคือใช่ สิ่งนั้นก็คือเอนทิตี

อ็อบเจ็กต์ค่า

อ็อบเจ็กต์ค่าเป็น สิ่งที่ไม่เปลี่ยนรูป และ ตรวจสอบความถูกต้องได้ด้วยตนเอง คุณสมบัติ readonly ของ PHP 8.1 จะบังคับให้ข้อมูลไม่เปลี่ยนรูป ส่วนตัวสร้างจะป้องกันเงื่อนไขคงตัว ทำให้ไม่มีทางสร้างอินสแตนซ์ที่ไม่ถูกต้องขึ้นมาได้

<?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

ความเท่าเทียมตามค่า

อ็อบเจ็กต์ค่าเปรียบเทียบกันด้วย ความเท่าเทียมเชิงโครงสร้าง ไม่ใช่การอ้างอิง ควรจัดเตรียมเมธอด equals() อย่างชัดเจน แทนการพึ่งพา == เนื่องจากการเปรียบเทียบแบบหลวมของ PHP มีกฎที่คาดเดาได้ยาก อ็อบเจ็กต์ค่า Money ต้องพิจารณาทั้งจำนวนเงินและสกุลเงิน

<?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

ความไม่เปลี่ยนรูปและเมธอดสำหรับสร้างค่าที่ปรับแล้ว

เนื่องจากอ็อบเจ็กต์ค่าไม่เปลี่ยนรูป การ "เปลี่ยนแปลง" อ็อบเจ็กต์หนึ่งจึงหมายถึงการคืนค่าเป็น อินสแตนซ์ใหม่ เมธอดอย่าง add() หรือ withAmount() จะไม่แก้ไขข้อมูลเดิม แต่จะสร้างค่าใหม่แทน วิธีนี้ช่วยขจัดข้อผิดพลาดจากการอ้างอิงร่วม และทำให้อ็อบเจ็กต์ค่าปลอดภัยต่อการใช้ร่วมกันอย่างอิสระทั่วทั้งโดเมน

<?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;

เอนทิตี

เอนทิตี ถูกกำหนดด้วยอัตลักษณ์ ซึ่งโดยทั่วไปคือรหัสที่สร้างโดยโดเมน ไม่ใช่รหัสที่เพิ่มขึ้นอัตโนมัติจากฐานข้อมูล การเปรียบเทียบความเท่าเทียมจะเปรียบเทียบรหัส แอตทริบิวต์ของเอนทิตีสามารถเปลี่ยนแปลงผ่านเมธอดที่มีพฤติกรรมและปกป้องเงื่อนไขคงตัวได้

<?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

การสร้างอัตลักษณ์

ควรสร้างอัตลักษณ์ภายในโดเมน เช่น UUID ก่อนการบันทึกลงที่จัดเก็บ แทนที่จะรอฐานข้อมูล วิธีนี้ช่วยให้คุณสร้างเอนทิตีที่ถูกต้องสมบูรณ์ในหน่วยความจำ อ้างอิงเอนทิตีนั้นข้ามแอกกริเกต และทดสอบได้โดยไม่ต้องใช้ฐานข้อมูล รหัสประจำตัวเป็นส่วนหนึ่งของแบบจำลอง ไม่ใช่สิ่งที่เกิดขึ้นจากการจัดเก็บข้อมูล

พฤติกรรมที่สมบูรณ์ ไม่ใช่ข้อมูลไร้พฤติกรรม

รูปแบบที่มักพบและควรหลีกเลี่ยงคือ แบบจำลองโดเมนที่ไร้พฤติกรรม: เอนทิตีเป็นเพียงกลุ่มตัวอ่านและตัวกำหนดค่าที่เปิดให้เข้าถึงได้ ขณะที่ตรรกะทั้งหมดอยู่ในคลาส "บริการ" DDD ผลักดันให้ใส่พฤติกรรม เข้าไปใน เอนทิตี $order->cancel() จึงห่อหุ้มกฎการยกเลิกไว้ แทนที่จะปล่อยให้ผู้เรียกเปลี่ยนฟิลด์สถานะโดยตรง

<?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

อ็อบเจ็กต์ค่าทั่วทั้งระบบ

แทนที่การยึดติดกับชนิดข้อมูลพื้นฐานด้วยอ็อบเจ็กต์ค่า แทนที่จะส่ง string $email int $cents และ string $currency ไปทั่วระบบ ให้ห่อหุ้มข้อมูลเหล่านี้ไว้ใน Email และ Money ประโยชน์ที่ได้รับ:

  • ตรวจสอบความถูกต้องเพียงครั้งเดียวเมื่อสร้าง
  • กฎของโดเมน เช่น การจับคู่สกุลเงิน อยู่ร่วมกับข้อมูล
  • ลายเซ็นชนิดข้อมูลสื่อเจตนาได้ชัดเจนและป้องกันการใช้ข้อมูลสับสนกัน

หมายเหตุการแมปเพื่อการจัดเก็บ

อ็อบเจ็กต์ค่ามักแมปกับ คอลัมน์แบบฝัง แทนการมีตารางของตนเอง เช่น #[Embeddable] ของด็อกทริน ส่วนเอนทิตีจะแมปกับแถวข้อมูลที่มีอัตลักษณ์เป็นกุญแจ อย่านำข้อกังวลด้านการจัดเก็บมาไว้ในอ็อบเจ็กต์โดเมนโดยตรง แบบจำลองไม่ควรรู้ว่าตนเองถูกจัดเก็บอยู่ใน MySQL เครื่องมือแมปออบเจ็กต์-เชิงสัมพันธ์หรือตัวแมปที่เขียนขึ้นเองจะทำหน้าที่แปลงระหว่างโดเมนที่มีพฤติกรรมสมบูรณ์กับฐานข้อมูล

อ็อบเจ็กต์ค่าแบบประกอบ

อ็อบเจ็กต์ค่าประกอบกันได้ Address จะรวมชนิดข้อมูลพื้นฐานหลายรายการเป็นแนวคิดเดียวที่สอดคล้องกัน พร้อมการเปรียบเทียบความเท่าเทียมและการจัดรูปแบบของตนเอง จากนั้นเอนทิตีจะเก็บชนิดข้อมูลที่มีความหมายครบถ้วนเพียงชนิดเดียว แทนการเก็บสตริงที่ไม่เชื่อมโยงกันห้ารายการ และกฎของที่อยู่จะอยู่รวมกันในที่เดียว

<?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;

ตรวจสอบอย่างรวดเร็ว

เอนทิตีหรืออ็อบเจ็กต์ค่า?

สรุปทบทวน

คุณได้เรียนรู้หน่วยพื้นฐานหลักสองอย่างของ DDD แล้ว เอนทิตี ถูกกำหนดด้วยอัตลักษณ์ที่คงที่ มีพฤติกรรมที่สมบูรณ์ และปกป้องเงื่อนไขคงตัวผ่านเมธอด อ็อบเจ็กต์ค่า ไม่เปลี่ยนรูป ตรวจสอบความถูกต้องได้ด้วยตนเอง และเปรียบเทียบกันด้วยความเท่าเทียมของแอตทริบิวต์ จึงช่วยแทนที่การยึดติดกับชนิดข้อมูลพื้นฐาน การสร้างแบบจำลองอย่างถูกต้อง โดยใส่ตรรกะไว้ในอ็อบเจ็กต์แทนการใช้กลุ่มข้อมูลไร้พฤติกรรม เป็นรากฐานของแอกกริเกต รีโพซิทอรี และส่วนที่เหลือของชั้นโดเมน

เริ่มต้นได้ฟรี

เรียนรู้ PHP ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
49
บทเรียน
195

คำถามที่พบบ่อย

บทเรียน “องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า”

จำลองโดเมนด้วยเอนทิตีที่มีพฤติกรรมครบถ้วนและออบเจ็กต์ค่าที่เปลี่ยนแปลงไม่ได้ คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม

ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. องค์ประกอบพื้นฐาน DDD: เอนทิตีและออบเจ็กต์ค่า
  2. แอกกริเกต รีโพซิทอรี และแฟกทอรี
  3. เหตุการณ์โดเมนและบริการโดเมน
  4. บริบทที่มีขอบเขตและการทำแผนผังบริบท
← กลับไปที่ PHP Academy