التجميعات والمستودعات والمصانع
احمِ الثوابت باستخدام التجميعات واحفظها بطريقة سليمة.
التجميعات والمستودعات والمصانع درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.
حماية الثوابت
بعد إنشاء الكيانات وكائنات القيمة، تحتاج إلى أنماط تحافظ على اتساق مجموعاتها، وتتيح تخزينها بطريقة سليمة. يقدّم DDD ثلاثة أنماط تكتيكية لذلك: التجميع، وهو حد الاتساق؛ والمستودع، وهو تجريد للتخزين يشبه مجموعة؛ والمصنع، الذي يتولى الإنشاء المعقّد. يوضّح هذا الدرس كيفية تعاونها في PHP.
ما هو التجميع
التجميع هو مجموعة من الكيانات وكائنات القيمة تُعامَل كوحدة واحدة عند تغيير البيانات. ويكون أحد الكيانات هو جذر التجميع، وهو العضو الوحيد الذي يمكن للشيفرة خارج التجميع الاحتفاظ بمرجع إليه. تمر جميع التعديلات عبر الجذر، الذي يفرض ثوابت التجميع. كما أن التجميع هو حد المعاملة؛ إذ يُحمَّل ويُحفَظ بصورة ذرّية.
الجذر يحمي التجميع بأكمله
لا تلمس الجهات المستدعية الخارجية الأعضاء الداخليين مباشرة. ولإضافة بند، تستدعي طريقة على الجذر، الذي يتحقق من صحة البيانات ويحافظ على الاتساق، مثل الإجماليات والحدود. وهكذا تبقى الثوابت في مكان واحد.
<?php
final class LineItem {
public function __construct(
public readonly string $sku,
public readonly int $qty,
public readonly int $unitCents
) {}
public function subtotal(): int { return $this->qty * $this->unitCents; }
}
final class Order { // Aggregate Root
/** @var LineItem[] */
private array $items = [];
public function __construct(public readonly string $id) {}
public function addItem(string $sku, int $qty, int $unitCents): void {
if ($qty < 1) { throw new DomainException('qty must be >= 1'); }
$this->items[] = new LineItem($sku, $qty, $unitCents);
}
public function total(): int {
return array_sum(array_map(fn(LineItem $i) => $i->subtotal(), $this->items));
}
}
$o = new Order('o1');
$o->addItem('A', 2, 500);
$o->addItem('B', 1, 300);
echo $o->total(), PHP_EOL; // 1300
تصميم تجميعات صغيرة
من الأخطاء المتكررة جعل التجميعات كبيرة جدًا، مثل جعل Order يمتلك أيضًا الرسم البياني الكامل للكائنات الخاص بـ Customer. القواعد الإرشادية:
- أبقِ التجميعات صغيرة؛ وأشِر إلى التجميعات الأخرى باستخدام المعرّف بدلًا من الاحتفاظ بالكائن.
- ينبغي لمعاملة واحدة تعديل تجميع واحد؛ ونسّق بين التجميعات باستخدام أحداث النطاق.
- تحدد الثوابت التي يجب أن تظل صحيحة دائمًا حدود التجميع.
الإشارة باستخدام المعرّف
يخزّن الطلب كائن قيمة باسم customerId، لا كيان Customer. ويحافظ ذلك على ضيق حد الاتساق، ويتجنب تحميل رسوم بيانية ضخمة للكائنات. ويصبح الاتساق بين التجميعات اتساقًا نهائيًا، تتولاه الأحداث بدلًا من معاملة واحدة ضخمة.
<?php
final class CustomerId {
public function __construct(public readonly string $value) {}
}
final class Order {
public function __construct(
public readonly string $id,
public readonly CustomerId $customerId // reference, not object
) {}
}
$order = new Order('o1', new CustomerId('cus_99'));
echo $order->customerId->value, PHP_EOL;
عقد المستودع
يمنح المستودع انطباعًا بوجود مجموعة من جذور التجميع داخل الذاكرة. ولا يعتمد النطاق إلا على الواجهة؛ أما التطبيق، سواء باستخدام Doctrine أو PDO أو الذاكرة، فيعيش في طبقة البنية التحتية. وتتعامل المستودعات مع التجميعات الكاملة، وليس مع صفوف جزئية أبدًا.
<?php
interface OrderRepository {
public function ofId(string $id): ?Order;
public function save(Order $order): void;
public function nextIdentity(): string;
}
تطبيق داخل الذاكرة
يُعد المستودع الموجود داخل الذاكرة ذا قيمة كبيرة لاختبارات الوحدات السريعة التي لا تحتاج إلى قاعدة بيانات. وبما أن النطاق يعتمد على الواجهة، يمكنك تبديل التطبيقات بحرية، وهو تطبيق عملي لمبدأ عكس الاعتماد.
<?php
interface OrderRepository {
public function ofId(string $id): ?object;
public function save(object $order): void;
public function nextIdentity(): string;
}
final class Order { public function __construct(public readonly string $id) {} }
final class InMemoryOrderRepository implements OrderRepository {
private array $store = [];
public function ofId(string $id): ?object { return $this->store[$id] ?? null; }
public function save(object $order): void { $this->store[$order->id] = $order; }
public function nextIdentity(): string { return 'o_' . bin2hex(random_bytes(4)); }
}
$repo = new InMemoryOrderRepository();
$repo->save(new Order('o1'));
var_dump($repo->ofId('o1') !== null);
المستودع ليس DAO
المستودع ليس DAO عامًا لعمليات CRUD. بل يوفّر استعلامات ذات معنى من منظور النطاق، مثل findOverdueOrders()، ويعيد تكوين التجميعات الكاملة مع الحفاظ على ثوابتها. وهو يخفي عمدًا تفاصيل SQL وORM حتى يظل النطاق غير معتمد على التخزين. تجنّب تسريب منشئات الاستعلام أو الطرائق العامة مثل save($anyEntity) إلى النطاق.
المصانع للإنشاء المعقّد
عندما يتضمن إنشاء تجميع منطقًا حقيقيًا، مثل إنشاء الهوية وتجميع كائنات القيمة وفرض الثوابت عند الإنشاء، فانقل ذلك إلى مصنع، سواء كان صنفًا مخصصًا أو مُنشئًا ثابتًا مسمّى. ويحافظ ذلك على بساطة مُنشئ الكيان، ويجمع قواعد الإنشاء الصالح في مكان واحد.
<?php
final class Order {
private function __construct(
public readonly string $id,
public readonly string $customerId
) {}
public static function place(string $customerId): self {
if ($customerId === '') { throw new DomainException('customer required'); }
return new self('o_' . bin2hex(random_bytes(4)), $customerId);
}
}
$order = Order::place('cus_1');
echo $order->id, PHP_EOL;
كيف تتعاون معًا
التدفق المعتاد في خدمة التطبيق:
- ينشئ المصنع، أو المُنشئ المسمّى، تجميعًا صالحًا.
- تفرض طرائق جذر التجميع الثوابت أثناء الاستخدام.
- يخزّن المستودع التجميع الكامل، ثم يعيد تكوينه لاحقًا.
تنسّق خدمة التطبيق هذه الخطوات داخل معاملة واحدة لكل تجميع، ولا تعتمد إلا على الواجهات.
فرض ثابت على مستوى التجميع بأكمله
تتمثل القيمة الحقيقية للجذر في فرض الثوابت التي تمتد عبر الأعضاء. ففي هذا المثال، يرفض الطلب إضافة بند إذا كان سيؤدي إلى تجاوز الإجمالي لحد الائتمان؛ وهي قاعدة لا يستطيع أي LineItem فرضها بمفرده. وبما أن جميع التغييرات تمر عبر الجذر، فلا يمكن تجاوز القاعدة أبدًا.
<?php
final class Order {
private array $items = [];
public function __construct(
public readonly string $id,
private int $creditLimitCents
) {}
public function addItem(int $cents): void {
if ($this->total() + $cents > $this->creditLimitCents) {
throw new DomainException('Exceeds credit limit');
}
$this->items[] = $cents;
}
public function total(): int { return array_sum($this->items); }
}
$o = new Order('o1', 1000);
$o->addItem(600);
try { $o->addItem(600); } catch (DomainException $e) { echo $e->getMessage(), PHP_EOL; }
echo $o->total(), PHP_EOL; // 600
اختبار سريع
تصميم التجميع.
مراجعة
لقد تعلّمت حماية الثوابت والتخزين بطريقة سليمة. تشكّل التجميعات حدًا للاتساق والمعاملة، ولا تُعدَّل إلا عبر جذورها، وتظل صغيرة من خلال الإشارة إلى التجميعات الأخرى باستخدام المعرّفات. وتعرض المستودعات جذور التجميع كمجموعة خلف واجهة من واجهات النطاق، فتخفي ORM وSQL وتتيح استخدام بدائل اختبار داخل الذاكرة. وتجمع المصانع عمليات الإنشاء المعقّدة التي تفرض الثوابت في مكان واحد. وتحافظ هذه العناصر مجتمعةً على اتساق نموذج النطاق وعدم اعتماده على التخزين.
الأسئلة الشائعة
هل درس «التجميعات والمستودعات والمصانع» مجاني؟
نعم — نص درس «التجميعات والمستودعات والمصانع» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.
ماذا ستتعلم في «التجميعات والمستودعات والمصانع»؟
احمِ الثوابت باستخدام التجميعات واحفظها بطريقة سليمة. تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟
لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «التجميعات والمستودعات والمصانع»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟
نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لبنات DDD الأساسية: الكيانات وكائنات القيمة
- التجميعات والمستودعات والمصانع
- أحداث النطاق وخدمات النطاق
- السياقات المحددة ورسم خرائط السياق