أهلاً بيك يا صديقي المطور في الدرس السابع من سلسلة معمارية إضافات ووردبريس المتقدمة. في الدرس السابق اتعلمنا سحر حقن التبعيات (Dependency Injection)، وعرفنا إزاي نفكك الكود ونخليه نقي وقابل للاختبار. لكن بعد ما تطبق DI في مشروع فيه 40 أو 50 كلاس، هتصدمك مشكلة جديدة بيسميها مهندسو البرمجيات **Wiring Hell** أو “جحيم توصيل الأسلاك”!
تلاقي نفسك في ملف الإقلاع بتكتب 200 سطر عشان بس تبني كائن واحد:
$logger = new Logger();
$db = new Database($wpdb, $logger);
$cache = new RedisCache($logger);
$repo = new OrderRepository($db, $cache);
$mailer = new SmtpMailer($logger);
$processor = new OrderProcessor($repo, $mailer, $logger);
الموضوع بيتحول لكابوس تكراري ممل جداً، ولو أي كلاس في النص غير معامل في الـ Constructor بتاعه، هتضطر تلف تعدل كل التوصيلات دي يدوي!
هنا بقى بيجي البطل المعماري الخارق: **حاوية الخدمات (Service Container)**. الحاوية دي هي المصنع الذكي المركزي اللي بيفهم شجرة التبعيات في مشروعك، وبيعرف إزاي يركب الكائنات ببعضها أوتوماتيكياً عبر تقنية **Auto-wiring** بالاعتماد على الـ Reflection في PHP!
في المقال ده، هنشرح بالعامية المصرية التقنية إزاي تبني حاوية خدمات ذكية متوافقة 100% مع معيار **PSR-11** الدولي، وإمتى مشروعك يكون محتاج حاوية فعلاً وإمتى يكون استخدامها مجرد تعقيد زائد (Over-engineering)، مع كود إنتاجي متكامل ودراسة حالة حقيقية لمنصة رعاية صحية بالدقي و20 سؤال وجواب لتثبيت كل مفاهيم الحاويات.
ما هي حاوية الخدمات (Service Container)؟ تشبيه المصنع الآلي
عشان تتخيل حاوية الخدمات ببساطة، تخيل إنك دخلت مصنع سيارات حديث؛ أنت مش بتروح للمهندس تقوله: “اصنع لي مسمار، وبعدين لحم المسمار في الماتور، وبعدين ركب الماتور في الشاسيه، وبعدين ركب العجل!”.
أنت ببساطة بتضغط على زرار وتقول للمصنع الآلي: “أنا محتاج عربية جاهزة!”.
المصنع الآلي هو اللي بيبص في الكتالوج بتاعه، ويعرف إن العربية محتاجة شاسيه، والشاسيه محتاج ماتور، والماتور محتاج مسامير؛ فيصنع كل حاجة بالترتيب الدقيق ويركبهم مع بعض ويسلمك العربية جاهزة تدورها وتتحرك فوراً!
ده بالضبط اللي بتعمله **Service Container (IoC Container)** في كود الإضافة:
أنت بتطلب منها:
$container->get(OrderProcessor::class);
الحاوية تروح تقرأ كلاس OrderProcessor، تكتشف إنه محتاج مستودع ومحتاج خدمة إيميلات؛ فتروح تصنعهم الأول وتحقنهم جواه أوتوماتيكياً وتسلمك الكائن كامل وجاهز في أجزاء من الميكروثانية!
| وجه المقارنة | التوصيل اليدوي (Manual Wiring) | حاوية الخدمات الآلية (Auto-wiring Container) |
|---|---|---|
| طريقة إنشاء الكائنات | كتابة new يدوياً لكل كائن وكل تبعية له بالتسلسل. | طلب الكلاس الرئيسي، والحاوية تتكفل بإنشاء شجرة التبعيات آلياً. |
| إدارة الذاكرة (Singletons) | الاحتفاظ بمراجع الكائنات في متغيرات عامة أو خصائص كلاسات. | إدارة دورة حياة الكائن مركزياً (تخزين الكائن المشترك وإعادة استخدامه). |
| تغيير كود الـ Constructor | يتطلب تعديل كل ملف في المشروع يحتوي على استدعاء يدوي لهذا الكلاس. | صفر تعديلات؛ الحاوية تكتشف المعامل الجديد وتقوم بحقنه فوراً عبر Reflection. |
| التوافق المعياري | خاص بمطور المشروع ولا يتطابق مع أي معيار دولي. | متوافق تماماً مع مواصفات PSR-11 المعتمدة من تحالف PHP-FIG. |
| سهولة التوسع | تصبح معقدة وكابوساً حقيقياً مع زيادة عدد الخدمات عن 20 خدمة. | مرنة ورشيقة جداً؛ تدعم إضافة مئات الخدمات ومزودي الخدمات (Providers). |
معيار PSR-11: العقد الموحد لحاويات الخدمات
مجموعة PHP-FIG وضعت المعيار PSR-11 لتوحيد طريقة تعامل المطورين مع حاويات الخدمات في كافة لغات وأطر عمل PHP.
المعيار بيفرض واجهة بسيطة جداً اسمها PsrContainerContainerInterface لا تحتوي إلا على دالتين فقط:
<?php
namespace PsrContainer;
interface ContainerInterface {
/**
* إيجاد خدمة بواسطة معرفها النصي أو اسم الكلاس وإعادتها جاهزة
*
* @param string $id معرف الخدمة أو FQCN
* @return mixed
* @throws NotFoundExceptionInterface لو الخدمة مش موجودة
* @throws ContainerExceptionInterface لو حصل خطأ أثناء بناء الخدمة
*/
public function get(string $id);
/**
* فحص هل الخدمة مسجلة في الحاوية أم لا
*
* @param string $id
* @return bool
*/
public function has(string $id): bool;
}
الميزة هنا إنك لو بنيت إضافتك متوافقة مع PSR-11، تقدر في أي وقت تستبدل الحاوية بتاعتك بحاوية احترافية جاهزة زي PHP-DI أو Laravel Container أو Symfony Service Container بدون ما تغير سطر كود واحد في خدماتك!
متى تحتاج حاوية خدمات فعلاً في ووردبريس؟
المطور الحكيم يعرف متى يستخدم الأداة ومتى يتجنبها؛ الحاوية مش لعبة بنحطها في كل مكان:
حالات لا تحتاج فيها لحاوية خدمات (تعتبر Over-engineering):
- الإضافات البسيطة التي تؤدي وظيفة واحدة محددة (مثل إضافة زر واتساب أو إضافة كود Google Analytics).
- الإضافات التي لا تتجاوز 4 أو 5 كلاسات بسيطة لا تحتوي على تشابك معقد في التبعيات.
- القوالب البسيطة ومواقع المدونات الخفيفة.
حالات تصبح فيها حاوية الخدمات ضرورة حتمية (Enterprise Grade):
- منصات التجارة الإلكترونية، بوابات الدفع، وإضافات أنظمة الشحن المتعددة.
- إضافات الـ ERP وحسابات الشركات وإدارة الفواتير الضريبية.
- المشاريع التي تحتوي على أكثر من 25 خدمة ومستودع بيانات وعشرات الـ Controllers في الـ REST API.
- المشاريع التي يعمل عليها فريق تطوير كبير يحتاج إلى استبدال خدمات حقيقية بخدمات تجريبية أثناء العمل المشترك.
السحر الأسود للحاويات: الاستنتاج التلقائي (Auto-wiring via Reflection)
إزاي الحاوية بتقدر تعرف الكلاس محتاج إيه بدون ما نكتب لها كود؟
السر يكمن في فئة تابعة للغة PHP اسمها ReflectionClass.
لما تطلب من الحاوية كلاس OrderService:
- الحاوية بتعمل
$reflector = new ReflectionClass($className);. - بتسأل: هل الكلاس قابل للإنشاء (Instantiable) ولا هو Interface/Abstract؟
- بتفحص الـ Constructor وتجيب مصفوفة المعاملات:
$constructor->getParameters();. - لكل معامل، بتسأل: إيه هو نوع المعامل (Type-hint)؟
- لو المعامل عبارة عن كلاس تاني (مثلاً
InvoiceMailer)، الحاوية بتروح تنادي على نفسها بشكل متكرر (Recursion):$this->get('InvoiceMailer')! - بعد ما تجمع كل الكائنات المطلوبة، بتستدعي:
$reflector->newInstanceArgs($dependencies);. - وتسلمك الكائن في رمشة عين!
الكود البرمجي الكامل: بناء حاوية خدمات PSR-11 إنتاجية بتقنية Auto-wiring
تعال نبني كود إنتاجي متكامل وخفيف لحاوية خدمات تدعم تسجيل الـ Singletons، وربط الـ Interfaces، والربط التلقائي عبر الـ Reflection:
<?php
declare(strict_types=1);
namespace AbozaidContainer;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionParameter;
use Exception;
// استثناءات معيار PSR-11 القياسية
class ContainerException extends Exception {}
class NotFoundException extends Exception {}
final class ServiceContainer {
/**
* مصفوفة لتخزين الروابط بين الواجهات والكلاسات المحددة
* @var array
*/
private array $bindings = [];
/**
* مصفوفة لتخزين الكائنات المشتركة (Singletons)
* @var array
*/
private array $instances = [];
/**
* ربط واجهة أو اسم خدمة بكلاس محدد أو كولباك
*/
public function bind(string $abstract, string|callable|null $concrete = null): void {
$this->bindings[$abstract] = $concrete ?? $abstract;
}
/**
* ربط خدمة كـ كائن وحيد مشترك (Singleton)
*/
public function singleton(string $abstract, string|callable|null $concrete = null): void {
$this->bind($abstract, $concrete);
// نحدد أنه singleton ليتم تخزينه عند أول طلب
$this->instances[$abstract] = null;
}
/**
* تسجيل كائن تم إنشاؤه مسبقاً في الحاوية
*/
public function instance(string $abstract, object $instance): void {
$this->instances[$abstract] = $instance;
}
/**
* فحص هل الخدمة مسجلة أو قابلة للاستنتاج
*/
public function has(string $id): bool {
return isset($this->bindings[$id]) || isset($this->instances[$id]) || class_exists($id);
}
/**
* استخراج وبناء الخدمة مع كافة تبعياتها تلقائياً (Auto-wiring)
*/
public function get(string $id): object {
// 1. لو الخدمة مسجلة كـ Singleton وتم بناؤها مسبقاً، أعدها فوراً من الذاكرة
if (isset($this->instances[$id]) && $this->instances[$id] !== null) {
return $this->instances[$id];
}
// 2. تحديد الكلاس الفعلي المرتبط بالمعرف
$concrete = $this->bindings[$id] ?? $id;
// 3. لو كان الرابط عبارة عن كولباك مصنع (Factory Closure)
if (is_callable($concrete)) {
$object = $concrete($this);
} else {
// 4. بناء الكائن عبر الـ Reflection
$object = $this->build($concrete);
}
// 5. حفظ الكائن إذا كان مسجلاً كـ Singleton
if (array_key_exists($id, $this->instances)) {
$this->instances[$id] = $object;
}
return $object;
}
/**
* استخدام PHP Reflection لتحليل المنشئ وحقن التبعيات آلياً
*/
private function build(string $concrete): object {
if (!class_exists($concrete)) {
throw new NotFoundException("الكلاس المطلوب غير موجود ولا يمكن بناؤه: {$concrete}");
}
$reflector = new ReflectionClass($concrete);
if (!$reflector->isInstantiable()) {
throw new ContainerException("لا يمكن إنشاء كائن من هذا الكلاس (قد يكون Interface أو Abstract): {$concrete}");
}
$constructor = $reflector->getConstructor();
// لو الكلاس ملوش Constructor، أنشئه فوراً بدون تبعيات
if ($constructor === null) {
return new $concrete();
}
// فحص معاملات المنشئ واستنتاجها
$parameters = $constructor->getParameters();
$dependencies = $this->resolveDependencies($parameters);
return $reflector->newInstanceArgs($dependencies);
}
/**
* حل مصفوفة المعاملات المطلوبة للمنشئ
*/
private function resolveDependencies(array $parameters): array {
$dependencies = [];
foreach ($parameters as $parameter) {
$type = $parameter->getType();
// التحقق من وجود Type-hint للكلاس
if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) {
if ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
continue;
}
throw new ContainerException("لا يمكن استنتاج قيمة المعامل الأولي [{$parameter->getName()}] تلقائياً بدون قيمة افتراضية.");
}
// استنتاج الكلاس المطلوب عبر استدعاء الحاوية ذاتياً (Recursion)
$dependencies[] = $this->get($type->getName());
}
return $dependencies;
}
}
معمارية مزودي الخدمات (Service Providers Architecture)
عشان تنظيم كود الحاوية ميبقاش ملف واحد ضخم، بنقسم تسجيل الخدمات إلى **Service Providers** مخصصين؛ كل Provider مسؤول عن حزمة خدمات معينة:
bind(SmsGatewayInterface::class, VodafoneSmsGateway::class);
// تسجيل مستودع المواعيد كـ Singleton مشترك مع تمرير كائن $wpdb
global $wpdb;
$container->singleton(AppointmentRepositoryInterface::class, function() use ($wpdb) {
return new WpdbAppointmentRepository($wpdb);
});
}
}
دراسة حالة حقيقية: إنقاذ منصة حجز عيادات طبية بالدقي
تعال أحكي لك قصة واقعية من قلب حي الدقي بالجيزة لشركة ناشئة بتقدم منصة لإدارة وحجز مواعيد العيادات الطبية والمراكز العلاجية بتخدم أكتر من 350 عيادة خاصة في القاهرة والإسكندرية:
الوضع المأساوي:
المنصة كانت مبنية بإضافة ووردبريس مخصصة ضخمة فيها أكتر من 65 كلاس؛ كلاسات لحجز الكشف، وكلاسات لتأكيد الميعاد بالواتساب والرسائل، وكلاسات للدفع بفيزا وفوري، وكلاسات لتقارير الأطباء وسجلات المرضى.
مبرمج الإضافة السابق كان عامل ملف إقلاع اسمه bootstrap-wiring.php جواه أكتر من 850 سطر كود كلها عبارة عن $serviceA = new A(); $serviceB = new B($serviceA); ...!
الكارثة:
شركة الاتصالات اللي بتبعت رسائل الـ SMS غيرت الـ API بتاعها، وطلب مدير الشركة الانتقال الفوري لمزود رسائل جديد خلال 24 ساعة.
المبرمجين دخلوا يعدلوا كلاس الرسائل في bootstrap-wiring.php، وبسبب تشابك التبعيات اليدوية، غلطوا في ترتيب 3 كائنات!
النتيجة كانت إن نظام حجز العيادات اتعطل بالكامل في وقت الذروة الصباحية؛ المرضى مش عارفين يحجزوا، والأطباء في العيادات فتحوا لوحة التحكم لقوا كل جداول المواعيد بيضاء! والمنصة توقفت لمدة يوم كامل، وتسببت في إلغاء أكثر من 1,200 كشف طبي وخسارة عملاء لصالح منصات منافسة (زي فيزيتا).
الحل المعماري باستخدام Service Container:
- بناء الحاوية المتوافقة مع PSR-11: قمنا بإلغاء ملف التوصيل اليدوي القديم بالكامل (مسح الـ 850 سطر!).
- تقسيم الإضافة إلى Service Providers: أنشأنا 4 مزودين منظمين (DatabaseProvider, NotificationProvider, PaymentProvider, AdminProvider).
- تفعيل ميزة Auto-wiring: أصبحت الحاوية تستنتج كافة التبعيات تلقائياً عبر الـ Reflection.
النتيجة الساحرة:
انخفضت أسطر ملف التهيئة من 850 سطر يدوي هش إلى **25 سطر كود فقط**! وعندما طلبوا تغيير مزود الرسائل مرة أخرى لاحقاً، تم التغيير في سطر واحد فقط داخل NotificationProvider في دقيقتين ونصف بدون لمس أي سطر كود آخر في المنصة، مع استقرار تام وأداء فائق.
الوصايا العشر لاستخدام حاويات الخدمات (Service Containers) في ووردبريس
- لا تفرط في الاستخدام (Avoid Over-engineering): لا تستخدم الحاوية إذا كانت إضافتك صغيرة وتضم بضعة كلاسات بسيطة.
- التزم بمعيار PSR-11: اجعل حاويتك تنفذ
PsrContainerContainerInterfaceلضمان التوافق القياسي وسهولة الاستبدال. - احذر تحويل الحاوية إلى Service Locator: لا تمرر كائن الحاوية داخل خدماتك لتبحث هي بداخله، بل دع الحاوية هي التي تحقن التبعيات.
- اعتمد على الاستنتاج التلقائي (Auto-wiring): استخدم قدرات Reflection الذكية لتوفير آلاف الأسطر من التوصيل اليدوي الممل.
- حدد دورة حياة الكائنات بدقة: ميز بين الكائنات التي يجب أن تكون Singletons (كالخدمات والمستودعات) والكائنات المتجددة ذات الحالة (Stateful).
- نظم خدماتك عبر Service Providers: وزع تسجيل الخدمات على كلاسات مزودين متخصصة لتسهيل الصيانة والعمل الجماعي.
- احذر من تكرار الحاويات: اجعل لإضافتك حاوية مركزية واحدة يتم تهيئتها في كلاس النواة الرئيسي عند إقلاع الإضافة.
- عالج مشكلة المعاملات الأولية (Primitives): إذا كان الكلاس يستقبل نصوصاً أو أرقاماً في المنشئ، وفر قيماً افتراضية أو اربطها صراحة في الحاوية عبر Factory Closures.
- استخدم الكاش للكائنات الثقيلة: الحاوية توفر في الذاكرة عبر إعادة استخدام كائنات الـ Singletons بدلاً من إنشاء كائنات مكررة مع كل ريكوست.
- اختبر إمكانية استبدال الخدمات: الحاوية الممتازة هي التي تتيح لك استبدال خدمة الدفع الحقيقية بخدمة وهمية في الاختبارات بسطر كود واحد فقط.
الربط السياقي (Contextual Binding): حل معضلة الواجهات المتعددة
واحدة من أعقد المشاكل التي تواجه المطورين عند تطبيق حاويات الخدمات هي: **ماذا لو كان لدي كلاسين مختلفين، وكل كلاس منهما يحتاج تنفيذاً مختلفاً لنفس الواجهة (Interface)؟**
تخيل السيناريو الواقعي ده في متجر إلكتروني:
عندك واجهة تخزين موحدة اسمها StorageInterface.
كلاس SystemLogManager محتاج يحفظ ملفات اللوج على القرص الصلب المحلي للسيرفر عبر LocalStorage.
وفي نفس الوقت، كلاس CustomerInvoiceArchive محتاج يرفع الفواتير على سحابة أمازون عبر S3CloudStorage!
لو استخدمت الربط العادي $container->bind(StorageInterface::class, ...)، الحاوية هتحقن نفس النوع في الكلاسين!
الحل المعماري هو **الربط السياقي (Contextual Binding)**؛ حيث تضع الحاوية قاعدة ذكية تقول:
*”عندما يطلب كلاس LogManager واجهة التخزين، احقن له كائن LocalStorage. وعندما يطلب كلاس InvoiceArchive نفس الواجهة، احقن له S3CloudStorage!”*.
when(SystemLogManager::class)
->needs(StorageInterface::class)
->give(LocalStorage::class);
$container->when(CustomerInvoiceArchive::class)
->needs(StorageInterface::class)
->give(S3CloudStorage::class);
هذا المستوى من الذكاء المعماري يمنح نظامك أقصى درجات المرونة دون الحاجة لكتابة كود مكرر أو كسر مبدأ المسؤولية الواحدة.
نمط الخدمات الموسومة (Tagged Services): إضافة ميزات دون تعديل كود
لو أنت بتبني منصة تقارير أو بوابات شحن، وعايز تسمح للمطورين بإضافة شركات شحن جديدة لمشروعك بمجرد إنشاء الكلاس دون لمس الكلاس المركزي المسؤول عن حساب الأسعار، هنا بنستخدم نمط **الوسوم (Service Tags)**.
بدل ما تسجل كل شركة شحن بشكل منفرد في الكلاس المركزي، بتسجل الكلاسات في الحاوية وتعطيها وسماً مشتركاً (Tag):
tag([AramexCarrier::class, BostaCarrier::class, MylerzCarrier::class], 'shipping.carriers');
وفي كلاس محرك الشحن المركزي ShippingManager، بتطلب من الحاوية جلب كافة الخدمات التي تحمل هذا الوسم دفعة واحدة:
carriers as $carrier) {
$rates[$carrier->getName()] = $carrier->getRate($city, $weight);
}
return $rates;
}
}
شايف العظمة الهندسية؟ بكرة الصبح لو حبيت تضيف شركة شحن رابعة (زي البريد المصري)، كل اللي هتعمله إنك هتعمل كلاس جديد وتديله نفس الـ Tag في الـ Provider، والمحرك المركزي هيتعرف عليه أوتوماتيكياً ويشغله فوراً بدون ما تلمس سطر واحد في كود حساب الأسعار! وده التطبيق الحرفي والمثالي لمبدأ Open/Closed Principle.
التحميل الكسول للخدمات (Lazy Service Instantiation) لتوفير الذاكرة
في حاويات الخدمات الكبرى، من الأخطاء الفادحة إنشاء مثيلات كافة الكلاسات عند إقلاع الإضافة. المعمارية الصحيحة هي تفعيل Lazy Loading بحيث لا يتم إنشاء كائن الخدمة أو الاتصال بالـ API الخارجي إلا في اللحظة التي يُطلب فيها ذلك الكائن بالفعل.
الأسئلة الشائعة حول حاويات الخدمات في ووردبريس (FAQ)
1. ما هي حاوية الخدمات (Service Container) باختصار؟
هي أداة برمجية مركزية مسؤولة عن إدارة إنشاء الكائنات، وحقن التبعيات المطلوبة لكل كلاس تلقائياً، والتحكم في دورة حياة الكائنات المشتركة (Singletons) داخل التطبيق.
2. ما هو معيار PSR-11 وما هي دواله الأساسية؟
هو معيار دولي صادر عن تحالف PHP-FIG لتوحيد واجهات حاويات الخدمات، ويحتوي على دالتين فقط: get($id) لجلب وبناء الخدمة، و has($id) للتحقق من وجود الخدمة.
3. ما هي ميزة التوصيل التلقائي (Auto-wiring) وكيف تعمل؟
هي قدرة الحاوية على قراءة منشئ الكلاس تلقائياً عبر فئة ReflectionClass في PHP واكتشاف الكلاسات المطلوبة في المعاملات وبنائها وحقنها تلقائياً دون كتابة كود يدوي.
4. لماذا يعتبر نمط Service Locator ممارسة غير محبذة؟
لأنه يجعل الكلاس يعتمد مباشرة على كائن الحاوية ويبحث عن خدماته بداخلها، مما يخفي التبعيات الحقيقية للكلاس ويجعل اختباره وعزله أمراً صعباً ومعقداً.
5. ما هو دور مزود الخدمة (Service Provider) في معمارية الإضافات؟
هو كلاس تنظيمي وظيفته تجميع تسجيل وربط الخدمات المتقاربة (مثل خدمات الدفع، أو خدمات قواعد البيانات) داخل الحاوية في مكان واحد مفصول عن بقية النظام.
6. هل يسبب استخدام Reflection في الحاوية بطئاً ملحوظاً في السيرفر؟
تأثير الـ Reflection على الأداء ضئيل جداً ويقاس بأجزاء من الميكروثانية، ومع ذلك تقوم الحاويات المتقدمة بتخزين الكائنات المشتركة (Singletons) في الذاكرة لمنع تكرار التحليل في نفس الريكوست.
7. كيف أتعامل مع المعاملات النصية (مثل مفاتيح API) في الـ Auto-wiring؟
إما بوضع قيمة افتراضية لها في المنشئ، أو بتسجيل مصنع مخصص (Factory Closure) في الحاوية عبر $container->bind(MyClass::class, fn() => new MyClass($apiKey));.
8. ما هو الفرق بين $container->bind و $container->singleton؟
الأمر bind ينشئ كائناً جديداً في كل مرة تطلب فيها الخدمة، بينما singleton ينشئ الكائن مرة واحدة فقط عند أول طلب ويعيد نفس الكائن المخزن في كافة المرات التالية.
9. متى يكون استخدام حاوية الخدمات في ووردبريس مبالغة برمجية (Over-engineering)؟
عندما تكون الإضافة صغيرة جداً (أقل من 5 كلاسات) وتؤدي مهام بسيطة لا تتطلب حقن تبعيات معقد أو اختبارات وحدة مكثفة.
10. كيف أتعامل مع المتغير $wpdb داخل حاوية الخدمات؟
عبر تسجيله في الحاوية كـ Singleton في ملف الإقلاع باستخدام $container->instance('wpdb', $GLOBALS['wpdb']);، لتقوم الحاوية بحقنه تلقائياً في أي مستودع بيانات يطلبه.
11. هل يمكن استخدام حاويات خدمات شهيرة مثل PHP-DI في ووردبريس؟
نعم، يمكنك تثبيتها عبر Composer وعزلها باستخدام PHP-Scoper واستخدامها كنواة لإضافتك بدلاً من كتابة حاوية مخصصة من الصفر.
12. ماذا يحدث إذا كان هناك اعتماد دائري (Circular Dependency) بين كلاسين؟
ستدخل الحاوية في حلقة استدعاء لا نهائي (Infinite Recursion)؛ والحل هو فحص التصميم المعماري وإعادة الهيكلة أو استخدام Setter Injection لكسر الحلقة الدائرية.
13. كيف تساعد الحاوية في كتابة اختبارات الوحدة السريعة؟
تسمح لك في كود الاختبار بربط الواجهة بكائن وهمي (Mock) بضغطة زر واحدة: $container->bind(GatewayInterface::class, MockGateway::class); ليعمل النظام كاملاً بالكائن الوهمي فوراً.
14. أين يجب أن احتفظ بنسخة الحاوية في الإضافة؟
في كلاس نواة الإضافة المركزي (Plugin Bootstrapper)، ويتم تهيئتها وتشغيل مزودي الخدمات التابعين لها داخل هوك plugins_loaded.
15. كيف تتعامل الحاوية مع الكلاسات التي لا تملك Constructor؟
تكتشف الحاوية عبر الـ Reflection عدم وجود منشئ وتقوم فوراً بإنشاء الكائن مباشرة عبر new $className() دون أي استنتاجات إضافية.
16. هل تؤثر حاوية الخدمات على طريقة تسجيل هوكات ووردبريس؟
لا تؤثر سلباً، بل تجعلها أكثر أناقة؛ حيث تطلب كائن المشترك من الحاوية $subscriber = $container->get(OrderSubscriber::class); ثم تسجل دواله في الهوكات.
17. ماذا يعني مصطلح Inversion of Control (IoC)؟
عكس التحكم يعني نقل مسؤولية إنشاء وإدارة الكائنات من أيدي المطور والكلاسات الفردية إلى إطار عمل أو حاوية مركزية تدير المشهد بالكامل.
18. كيف تدعم الحاوية العمل في مشاريع الفرق الكبيرة؟
تسمح للمطورين بالاتفاق على الواجهات (Interfaces)، بحيث يقوم كل مطور ببناء كلاسه المنفصل وتقوم الحاوية بتجميعهم معاً دون الحاجة لتعديل ملفات بعضهم البعض.
19. كيف أمنع استدعاء Reflection المتكرر في بيئات الإنتاج عالية الحمل؟
يمكن بناء خريطة مسبقة للتبعيات وحفظها في كاش الذاكرة (Compiled Container) أثناء مرحلة الـ Build تماماً كما تفعل فريم ووركات Laravel و Symfony.
20. ما هي القاعدة الذهبية التي يجب أن يتذكرها كل معماري برمجيات ووردبريس؟
“دع الحاوية تقوم بالأعمال الروتينية الشاقة في توصيل الأسلاك، وركز عقلك وإبداعك في بناء منطق الأعمال الصافي الذي يصنع الفارق الحقيقي لعملائك!”
تفريغ الحاوية وعزل اختبارات الوحدة (Container State Isolation)
عندما تبدأ في كتابة اختبارات آلية مكثفة باستخدام PHPUnit لمشروع يعتمد على Service Container، هناك فخ أمني وتطويري شهير يقع فيه المطورون يسمى **تلوث حالة الاختبارات (Test State Pollution)**.
إذا كانت الحاوية تحتفظ بكائنات الـ Singletons في متغيرات ثابتة، فإن الاختبار الأول إذا قام بتعديل بيانات أو حالة كائن معين، ستظل هذه البيانات موجودة ومؤثرة على الاختبار الثاني والثالث، مما يؤدي إلى فشل اختبارات بريئة تماماً لأسباب غامضة يصعب تتبعها!
عشان تتفادى الكابوس ده، يجب تزويد الحاوية دائماً بدالة تفريغ صريحة مثل flush() أو reset()، واستدعاؤها داخل دالة tearDown() في PHPUnit بعد نهاية كل اختبار:
instances = [];
$this->bindings = [];
}
// داخل كلاس اختبارات PHPUnit
protected function tearDown(): void {
parent::tearDown();
// تصفير الحاوية بالكامل لضمان بيئة نقية للاختبار التالي
$this->container->flush();
}
هذا الإجراء البسيط يضمن لك استقلالية تامة لكل اختبار (Isolated Test Environment)، ويجعل نتائج الـ Unit Tests متطابقة وموثوقة بنسبة 100% سواء تم تشغيلها محلياً أو في خوادم الـ CI/CD المؤتمتة.


اترك تعليقاً
يجب أنت تكون مسجل الدخول لتضيف تعليقاً.