0Pricing
PHP Academy · درس

لبنات DDD الأساسية: الكيانات وكائنات القيمة

صمّم النموذج بإثراء الكيانات وبكائنات قيمة غير قابلة للتغيير.

لبنات DDD الأساسية: الكيانات وكائنات القيمة درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 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

عدم القابلية للتغيير وطرائق «with»

بما أن كائنات القيمة غير قابلة للتغيير، فإن «تغيير» أحدها يعني إرجاع مثيل جديد. فالطرائق مثل 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، قبل التخزين، بدلًا من انتظار قاعدة البيانات. يتيح لك ذلك إنشاء كيان صالح بالكامل في الذاكرة، والإشارة إليه عبر التجميعات، وإجراء الاختبارات دون قاعدة بيانات. فالمعرّف جزء من النموذج، وليس مجرد أثر من آثار التخزين.

سلوك غني، لا بيانات فقيرة

من الأنماط المضادة الشائعة نموذج النطاق الفقير: تكون الكيانات مجرد حاويات لطرائق getter وsetter عامة، بينما يوجد كل المنطق في أصناف «الخدمات». يدفع 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. الفوائد:

  • يحدث التحقق مرة واحدة عند الإنشاء.
  • تعيش قواعد النطاق، مثل تطابق العملات، مع البيانات.
  • توثّق تواقيع الأنواع الغرض وتمنع الخلط بين القيم.

ملاحظة حول تخطيط التخزين

غالبًا ما تُخطَّط كائنات القيمة إلى أعمدة مضمنة بدلًا من جداول خاصة بها، مثل Doctrine #[Embeddable]. أما الكيانات فتُخطَّط إلى صفوف تُعرَف بهويتها. أبقِ مخاوف التخزين خارج كائنات النطاق نفسها؛ فلا ينبغي للنموذج أن يعرف أنه مخزّن في MySQL. ويترجم ORM أو مُخطِّط مكتوب يدويًا بين النطاق الغني وقاعدة البيانات.

كائنات القيمة المركبة

يمكن تركيب كائنات القيمة معًا. إذ يجمع 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 الأساسيتين. تُعرَّف الكيانات بهوية مستقرة، وتحمل سلوكًا غنيًا، وتحمي ثوابتها من خلال الطرائق. أما كائنات القيمة فهي غير قابلة للتغيير، وتتحقق من صحتها ذاتيًا، وتُقارَن بتساوي السمات، فتستبدل الهوس بالأنواع البدائية. ويُعد نمذجة هذه العناصر بصورة صحيحة، مع وضع المنطق داخل الكائنات بدلًا من حاويات البيانات الفقيرة، أساسَ التجميعات والمستودعات وسائر طبقة النطاق.

الأسئلة الشائعة

هل درس «لبنات DDD الأساسية: الكيانات وكائنات القيمة» مجاني؟

نعم — نص درس «لبنات DDD الأساسية: الكيانات وكائنات القيمة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.

ماذا ستتعلم في «لبنات DDD الأساسية: الكيانات وكائنات القيمة»؟

صمّم النموذج بإثراء الكيانات وبكائنات قيمة غير قابلة للتغيير. تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟

لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «لبنات DDD الأساسية: الكيانات وكائنات القيمة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟

نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. لبنات DDD الأساسية: الكيانات وكائنات القيمة
  2. التجميعات والمستودعات والمصانع
  3. أحداث النطاق وخدمات النطاق
  4. السياقات المحددة ورسم خرائط السياق
← العودة إلى PHP Academy