أهلاً بيك يا صديقي المطور في الدرس السادس من سلسلة معمارية إضافات ووردبريس المتقدمة. النهاردة هنتكلم عن المفهوم اللي بيمثل حجر الأساس في هندسة البرمجيات الحديثة، والمفهوم اللي بيفصل بين “مبرمج بيكتب كود شغال وخلاص” وبين “مهندس برمجيات بيبني أنظمة تعيش لسنين”: **حقن التبعيات (Dependency Injection – DI)**.
أول ما بتسمع مصطلح “Dependency Injection” لأول مرة في الكتب والمقالات الأجنبية، بتحس إنه حاجة معقدة جداً وجاية من كوكب تاني ومليانة طلاسم! لكن الحقيقة الممتعة هي إن الفكرة كلها في منتهى البساطة والجمال:
بدل ما الكلاس بتاعك يدخل المطبخ ويطبخ اعتماداته بنفسه بكلمة new أو يستدعي متغيرات عامة بـ global، إحنا بنديله اعتماداته جاهزة على طبق من فضة من برة (بنحقنها فيه)!
المبدأ ده معروف في عالم التصميم البرمجي باسم **The Hollywood Principle** أو “مبدأ هوليوود”: *”ما تتصلش بينا، إحنا اللي هنتصل بيك ونبعت لك اللي أنت محتاجه!”*.
في المقال ده، هنشرح بالعامية المصرية التقنية إزاي كلمة new لما بتتحط في مكان غلط بتتحول لـ “غراء قاتل” بيلزق الكود في بعضه، وإزاي تفك الارتباط ده عبر **حقن المنشئ (Constructor Injection)**، وإزاي تحول نظامك لكود نقي وسهل الاختبار بنسبة 100% عبر كائنات الـ Mock، مع كود إنتاجي متكامل لنظام تقسيط، ودراسة حالة حقيقية لشركة أجهزة منزلية بالقاهرة و20 سؤال وجواب شاملين لكل صغيرة وكبيرة.
لماذا تعتبر كلمة new غراءً يقتل مرونة الكود؟
في البرمجة الكائنية، في مقولة شهيرة بتقول: **”The `new` keyword is glue”** (كلمة new هي عبارة عن صمغ أو غراء).
يعني إيه الكلام ده؟
لما تيجي في قلب كلاس اسمه OrderProcessor وتكتب السطر ده:
charge($orderId);
$mailer = new WpMailSender();
$mailer->sendReceipt($orderId);
}
}
أنت في اللحظة دي لحمت كلاس معالجة الطلبات في بوابة فوري وفي دالة الإيميل الأصلية لووردبريس بصمغ لا ينفصل!
التبعات الكارثية للتصميم ده:
- استحالة الاختبار الآلي (Untestable Code): لو حبيت تكتب Unit Test للـ
OrderProcessor، الاختبار هيتصل ببوابة فوري الحقيقية ويسحب فلوس حقيقية ويبعت إيميلات حقيقية للعملاء! مش هتعرف تستبدل فوري بكائن وهمي (Mock) أبداً لأن الـnewمحشورة جوة بطن الدالة. - انعدام المرونة (Zero Flexibility): لو العميل في المتجر اختار يدفع بـ Vodafone Cash أو البطاقة البنكية أو Paymob، هتضطر تفتح الكلاس وتكتب
if...elseمتداخلة ومعقدة تنتهك مبدأ Open/Closed Principle. - صعوبة الصيانة: لو بوابة فوري غيرت الباراميترات اللي بياخدها الـ Constructor بتاعها، هتضطر تلف على 20 كلاس في مشروعك وتعدل استدعاءات
newفي كل مكان!
| وجه المقارنة | الاعتماد المباشر بـ (new) | المتغيرات العامة (Globals) | حقن التبعيات (Constructor DI) |
|---|---|---|---|
| طريقة توفير التبعية | الكلاس ينشئ الكائن بنفسه داخلياً. | الكلاس يستدعي المتغير من الفضاء العام (global $wpdb). | يتم تمرير الكائن الجاهز من الخارج عبر الـ Constructor. |
| درجة الارتباط (Coupling) | ارتباط وثيق جداً بكلاس محدد (Concrete). | ارتباط أعمى ببيئة التشغيل والمتغيرات العامة. | ارتباط مفكك جداً بواجهات مجردة (Loose Coupling via Interfaces). |
| قابلية الاختبار (Testability) | شبه مستحيلة بدون آثار جانبية حقيقية. | معقدة جداً وتتطلب تزييف البيئة العامة (Global State Pollution). | فائقة السهولة؛ مرر Mock Object في الاختبار في جزء من الثانية. |
| شفافية الكود (Transparency) | مخفية؛ لا تعرف ما يحتاجه الكلاس إلا بقراءة سطوره. | مظلمة تماماً؛ يعتمد على متغيرات خارجية غير معلنة. | واضحة وضوح الشمس؛ متطلبات الكلاس معلنة في رأس المنشئ. |
أنواع حقن التبعيات (Flavors of Dependency Injection)
في هندسة البرمجيات، عندنا 3 طرق رئيسية لحقن التبعيات:
1. حقن المنشئ (Constructor Injection) – المعيار الذهبي المفضل 99%
وده الأسلوب اللي بنستخدمه في 99% من الحالات؛ بنمرر التبعيات الأساسية التي لا يمكن للكائن أن يعيش بدونها عبر دالة __construct().
الميزة هنا إن الكائن يستحيل إنشاؤه في حالة غير صالحة (Invalid State)، وبمجرد ما يتعمل له new من الخارج، بنكون متأكدين إنه جاهز للعمل فوراً:
<?php
final class OrderProcessor {
public function __construct(
private readonly PaymentGatewayInterface $gateway,
private readonly ReceiptMailerInterface $mailer
) {}
}
2. حقن التعيين (Setter / Method Injection)
بنستخدمه في التبعيات الاختيارية (Optional Dependencies)؛ يعني الكلاس يقدر يشتغل طبيعي بدونها، ولكن لو وفرتها له هيستخدمها (زي كائن اللوجات Logger أو نظام الكاش):
logger = $logger;
}
}
3. حقن الواجهات (Interface Injection)
وفيه الكلاس بينفذ واجهة تلزمه باستقبال التبعية (نادر الاستخدام في مشاريع PHP الحديثة لصالح حقن المنشئ).
الفارق الجوهري بين Dependency Injection ونمط Service Locator
كتير من المطورين بيفتكروا إنهم بيطبقوا DI، لكنهم في الحقيقة بيطبقوا نمط اسمه **Service Locator**، وهو نمط بيعتبره أغلب مهندسي المعمارية النظيفة (Anti-pattern).
شكل الـ Service Locator الخاطئ:
container->get('database');
$payment = $this->container->get('payment');
}
}
ليه ده مش DI نقي؟ لأن الكلاس هنا رجع خفى اعتماداته تاني! أي حد بيبص على المنشئ شايف إنه محتاج Container وبس، لكنه مش عارف هو محتاج إيه بالظبط من جوة الحاوية. ده غير إنه خلى الكلاس مقترن بكلاس الحاوية نفسه.
الحل النقي في Dependency Injection:
الكلاس لا يعرف أي شيء عن الـ Container إطلاقاً؛ الكلاس يطلب فقط DatabaseInterface و PaymentInterface في المنشئ، ونواة الإضافة أو الحاوية الخارجية هي اللي تحقنهم فيه في ملف التهيئة الرئيسي.
التطبيق البرمجي العملي: بناء محرك تقسيط متكامل بنمط Dependency Injection النقي
تعال نبني كود إنتاجي يوضح إزاي نبني نظام تقسيط طلبات للمتاجر، بنمط حقن المنشئ الصريح، مع عزل قاعدة البيانات وبوابة الدفع بنسبة 100%:
1. واجهات العقود المجردة (Contracts)
<?php
declare(strict_types=1);
namespace AbozaidInstallmentEngineContracts;
interface InstallmentRepositoryInterface {
public function recordInstallmentPlan(int $orderId, float $totalAmount, int $months, float $monthlyAmount): int;
public function isOrderEligible(int $orderId): bool;
}
interface PaymentDeductionInterface {
public function deductInitialDownPayment(int $customerId, float $amount): bool;
public function getGatewayName(): string;
}
interface CustomerNotifierInterface {
public function sendInstallmentWelcomeSchedule(int $customerId, array $scheduleDetails): bool;
}
2. كلاس خدمة التقسيط النقي (Pure Domain Service with DI)
لاحظ نقاء هذا الكلاس؛ خالي تماماً من ووردبريس، ويعلن عن كافة اعتماداته في المنشئ بأعلى درجات الوضوح:
<?php
declare(strict_types=1);
namespace AbozaidInstallmentEngineServices;
use AbozaidInstallmentEngineContractsInstallmentRepositoryInterface;
use AbozaidInstallmentEngineContractsPaymentDeductionInterface;
use AbozaidInstallmentEngineContractsCustomerNotifierInterface;
use InvalidArgumentException;
use RuntimeException;
final class InstallmentPlanManager {
public function __construct(
private readonly InstallmentRepositoryInterface $repository,
private readonly PaymentDeductionInterface $paymentGateway,
private readonly CustomerNotifierInterface $notifier,
private readonly float $annualInterestRate = 0.12 // فائدة سنوية 12%
) {}
/**
* معالجة وإنشاء خطة التقسيط للعميل
*/
public function setupPlan(int $orderId, int $customerId, float $orderTotal, int $months, float $downPayment): array {
if ($months 36) {
throw new InvalidArgumentException('فترة التقسيط يجب أن تتراوح بين 3 أشهر و 36 شهراً.');
}
if (!$this->repository->isOrderEligible($orderId)) {
throw new RuntimeException('الطلب الحالي غير مؤهل لنظام التقسيط.');
}
// 1. خصم الدفعة المقدمة عبر بوابة الدفع المحقونة
if ($downPayment > 0) {
$deducted = $this->paymentGateway->deductInitialDownPayment($customerId, $downPayment);
if (!$deducted) {
throw new RuntimeException('فشل في خصم الدفعة المقدمة عبر ' . $this->paymentGateway->getGatewayName());
}
}
// 2. حساب الفوائد والأقساط الشهرية
$remainingPrincipal = max(0.0, $orderTotal - $downPayment);
$totalInterest = $remainingPrincipal * ($this->annualInterestRate * ($months / 12));
$grandTotalWithInterest = $remainingPrincipal + $totalInterest;
$monthlyInstallment = round($grandTotalWithInterest / $months, 2);
// 3. حفظ الخطة في قاعدة البيانات عبر المستودع المحقون
$planId = $this->repository->recordInstallmentPlan(
$orderId,
$grandTotalWithInterest,
$months,
$monthlyInstallment
);
$planData = [
'plan_id' => $planId,
'order_id' => $orderId,
'months' => $months,
'monthly_installment' => $monthlyInstallment,
'down_payment' => $downPayment,
'total_amount' => $grandTotalWithInterest
];
// 4. إشعار العميل بجدول الأقساط عبر خدمة الإشعارات المحقونة
$this->notifier->sendInstallmentWelcomeSchedule($customerId, $planData);
return $planData;
}
}
3. كلاسات التنفيذ في بيئة ووردبريس (WordPress Adapters)
هنا نقوم بتوصيل ووردبريس الحقيقي بالواجهات:
db->prefix . 'installment_plans';
$this->db->insert($table, [
'order_id' => $orderId,
'total_amount' => $totalAmount,
'months_count' => $months,
'monthly_amount' => $monthlyAmount,
'created_at' => current_time('mysql')
]);
return (int) $this->db->insert_id;
}
public function isOrderEligible(int $orderId): bool {
// فحص حالة الطلب في ووردبريس
return true;
}
}
final class WpEmailCustomerNotifier implements CustomerNotifierInterface {
public function sendInstallmentWelcomeSchedule(int $customerId, array $scheduleDetails): bool {
$user = get_userdata($customerId);
if (!$user) return false;
$msg = sprintf('مرحباً بك! تم اعتماد خطة تقسيط طلبك بقسط شهري قدره %.2f ج.م على مدار %d شهراً.',
$scheduleDetails['monthly_installment'],
$scheduleDetails['months']
);
return wp_mail($user->user_email, 'جدول أقساطك المعتمد', $msg);
}
}
السحر الحقيقي لاختبارات الوحدة (Unit Testing with Mocks)
تعال شوف العظمة الحقيقية لـ Dependency Injection: إزاي بنختبر الكود الحسابي المعقد ده في 5 أسطر وبسرعة البرق وبدون لمس ووردبريس أو قواعد البيانات أو سحب فلوس من فيزا حقيقية:
createMock(InstallmentRepositoryInterface::class);
$mockRepo->method('isOrderEligible')->willReturn(true);
$mockRepo->expects($this->once())
->method('recordInstallmentPlan')
->willReturn(789); // إرجاع معرف خطة وهمي
$mockPayment = $this->createMock(PaymentDeductionInterface::class);
$mockPayment->expects($this->once())
->method('deductInitialDownPayment')
->with(55, 1000.0) // التحقق من خصم 1000 جنيه للعميل 55
->willReturn(true);
$mockNotifier = $this->createMock(CustomerNotifierInterface::class);
$mockNotifier->expects($this->once())
->method('sendInstallmentWelcomeSchedule')
->willReturn(true);
// 2. حقن التبعيات المزيفة في المنشئ
$manager = new InstallmentPlanManager($mockRepo, $mockPayment, $mockNotifier, 0.10); // فائدة 10%
// 3. التنفيذ واختبار الحسابات بدقة متناهية
// إجمالي الطلب 11,000 ج.م - دفعة مقدمة 1,000 ج.م = المتبقي 10,000 ج.م
// الفائدة لمدة 12 شهر (10%) = 1,000 ج.م -> الإجمالي 11,000 ج.م -> القسط الشهري = 916.67 ج.م
$result = $manager->setupPlan(101, 55, 11000.0, 12, 1000.0);
$this->assertEquals(789, $result['plan_id']);
$this->assertEquals(916.67, $result['monthly_installment']);
$this->assertEquals(11000.0, $result['total_amount']);
}
}
دراسة حالة حقيقية: كارثة شركة تقسيط الأجهزة بالمهندسين
عشان تعيش أهمية الكلام ده في دنيا البيزنس الحقيقي، هشاركك قصة واقعية حصلت مع شركة مصرية كبرى لتقسيط الأجهزة الكهربائية والإلكترونية مقرها في حي المهندسين بالجيزة:
الوضع القديم:
الشركة طورت إضافة ووردبريس لربط موقعها الإلكتروني مع محفظة إلكترونية وبوابة دفع بنكية محلية. المبرمج كان كاتب كود خصم الأقساط الشهرية بإنشاء كائن الدفع مباشرة داخل الدالة:
$gateway = new LiveBankPaymentGateway($merchantId, $secretKey);
الكارثة الكبرى في يوم التطوير:
الشركة عينت مبرمج Junior جديد عشان يطور ميزة احتساب نقاط المكافآت (Loyalty Points) للعملاء المنتظمين في السداد. المبرمج شغل بيئة الـ Staging وعمل تكرار لاختبار كود الأقساط 50 مرة عشان يتأكد إن النقاط بتتحسب صح!
المصيبة إن كود الأقساط مكنش فيه أي Dependency Injection، وكان بيستخدم الحساب الحقيقي للبنك في الإنتاج!
في خلال نص ساعة، السكربت خصم أكتر من 185,000 جنيه مصري من كروت ومحافظ عملاء حقيقيين في بنك مصر والبنك الأهلي بدون ما حد ياخد باله!
تليفونات خدمة العملاء انفجرت بالصراخ والتهديد ببلاغات مباحث الأموال العامة والبنك المركزي، ومدير الشركة كان هيدخل في أزمة قانونية طاحنة!
التدخل المعماري الحاسم:
لما مسكنا الكود:
- عزل الاتصال البنكي وراء
PaymentGatewayInterface: تم تحويل كود الدفع لواجهة مجردة. - تطبيق حقن التبعيات (Constructor DI) في كافة الخدمات: تم منع استخدام كلمة
newداخل كل كلاسات النظام نهائياً. - بناء بيئة كائنات وهمية (Mock Environment): في بيئة التطوير والـ Staging، النظام أوتوماتيكياً بيحقن
DummyPaymentGatewayتسجل العمليات في ملف نصي بدون أي اتصال بنكي حقيقي، بينما في بيئة الإنتاج فقط يتم حقنLiveBankPaymentGateway.
النتيجة: تم استرجاع أموال العملاء والاعتذار لهم، وحصلت الشركة على بيئة تطوير واختبار آمنة بنسبة 100% مستحيل يتسحب فيها مليم واحد بالخطأ، وسرعة كتابة الميزات الجديدة تضاعفت 4 مرات!
الوصايا العشر لتطبيق Dependency Injection في ووردبريس
- تذكر أن new هي غراء: لا تنشئ كائنات الخدمات الحساسة داخل كلاسات منطق الأعمال، بل اطلبها في المنشئ.
- اعتمد على حقن المنشئ (Constructor DI): هو الطريقة الأكثر أماناً وموثوقية لضمان اكتمال اعتمادات الكائن وقت ولادته.
- احقن الواجهات (Interfaces) وليس الكلاسات المحددة: اجعل كودك يعتمد على العقود لسهولة التبديل بين بيئات التطوير والإنتاج.
- امنع كود هوليوود المعكوس: لا تجعل كلاسك يتصل بالـ Container ويبحث عن الخدمات بنفسه، بل اجعل النظام يحقنها له.
- استفد من ميزة Constructor Promotion في PHP 8+: اختصر كود المنشئ باستخدام
public function __construct(private readonly ServiceInterface $service). - لا تخلط بين كائنات الكيان (Entities) والخدمات (Services): الكيانات البسيطة (مثل كائن User أو Product DTO) يمكن إنشاؤها بـ new بأمان، أما الخدمات التنسيقية فتُحقن.
- اكتب اختبارات وحدة مستمرة: إذا شعرت أن كتابة اختبار لكلاس معين تتطلب جهداً كبيراً، فهذا مؤشر فوري على وجود اعتمادات مخفية تحتاج لـ DI.
- حافظ على عدد اعتمادات منطقي: إذا وجدت المنشئ يستقبل أكثر من 5 أو 6 اعتمادات، فهذا يعني أن الكلاس يقوم بمهام كثيرة وينتهك مبدأ المسؤولية الواحدة (SRP).
- استخدم واجهات محددة وصغيرة (Interface Segregation): لا تجبر كلاسك على استقبال واجهة ضخمة تحتوي على 20 دالة وهو لا يحتاج سوى دالة واحدة.
- وثق اعتماداتك بدقة: اجعل رأس كل كلاس يشرح دور كل خدمة محقونة لتسهيل مراجعة الكود وفهمه من بقية أعضاء الفريق.
دورة حياة الكائنات المحقونة: المشتركة (Singleton) مقابل المتجددة (Transient)
لما تبدأ تطبق حقن التبعيات في إضافة كبيرة، هيقابلك قرار معماري مهم جداً في كل خدمة بتصممها:
**هل الخدمة دي كائن مشترك وحيد في الذاكرة (Singleton / Shared Instance)، ولا كائن متجدد بيتعمل منه نسخة جديدة مع كل حقن (Transient / Factory Instance)؟**
تعال نفهم الفرق الجوهري بينهم:
- الخدمات عديمة الحالة (Stateless Services – Singletons):
الكلاسات اللي مفيهاش بيانات خاصة بمستخدم معين؛ زي كلاس مستودع قاعدة البياناتInvoiceRepository، أو كلاس عميل الـ API لبوابة فوريFawryApiClient، أو كلاس حساب الضرائبTaxCalculator.
الكائنات دي مش بتحتفظ بأي حالة متغيرة في خواصها، وبالتالي من الذكاء الاقتصادي في الذاكرة إننا ننشئ منها كائن واحد فقط ونحقن نفس المرجع (Reference) في كل الكلاسات التانية اللي بتحتاجه. - الخدمات ذات الحالة (Stateful Objects – Transient / New Instances):
الكلاسات اللي بتحتفظ ببيانات معالجة خاصة بعملية معينة؛ زي كلاس التحقق من صحة الفورمOrderFormValidator، أو كائن السلةCartSessionContext.
لو عملت الكائن ده Singleton، بيانات الطلب الأول هتفضل بايتة في الكائن وتلوث معالجة الطلب التاني! عشان كده، الكائنات دي لازم يتعمل منها كائن جديد تماماً (Fresh Instance) في كل مرة يتم حقنها.
حقن القيم الأولية والإعدادات (Primitive & Configuration Injection)
حقن التبعيات مش بس للكائنات والكلاسات الكبيرة؛ المطور المحترف بيحقن كمان **القيم الأولية (Primitives)** زي مفاتيح الـ API، ونسب الضرائب، ومسارات السيرفر، وأوقات انتهاء المهلة (Timeouts).
بدل ما تيجي في نص الكود وتكتب:
$timeout = 15; // قيمة ثابتة محشورة في الكود
بنمرر القيم دي في منشئ الكلاس:
apiEndpoint, [
'body' => json_encode($payload),
'timeout' => $this->timeoutSeconds,
'headers' => [
'Authorization' => 'Bearer ' . $this->merchantKey,
'Content-Type' => 'application/json'
]
]);
if ($this->debugMode) {
error_log('[BANK DEBUG] Payload sent to: ' . $this->apiEndpoint);
}
return (array) $response;
}
}
الميزة هنا إنك لو عايز في بيئة الاختبار تغير الـ apiEndpoint لرابط Mock محلي، وتخلي الـ timeoutSeconds ثانيتين بس عشان الاختبار يخلص بسرعة، بتمررهم في المنشئ بمرونة تامة وبدون ما تعدل سطر كود واحد في كلاس العميل نفسه!
فوائد حقن التبعيات في كتابة اختبارات Mocking متقدمة
عندما تفصل التبعيات وتمررها عبر الـ Constructor، يمكنك بسهولة أثناء اختبارات PHPUnit إنشاء كائنات وهمية (Mock Objects) تحاكي استجابة قواعد البيانات أو بوابات الدفع دون تنفيذ عمليات حقيقية، مما يجعل الاختبارات سريعة ومستقلة تماماً.
الأسئلة الشائعة حول Dependency Injection في ووردبريس (FAQ)
1. ما هو التعريف المبسط لحقن التبعيات (Dependency Injection)؟
هو نمط تصميمي يقوم على تمرير الكائنات والخدمات التي يحتاجها الكلاس من الخارج (عادة عبر المنشئ __construct)، بدلاً من قيام الكلاس بإنشائها بنفسه داخلياً باستخدام كلمة new.
2. ما هي الفائدة الكبرى من استخدام Dependency Injection في تطوير إضافات ووردبريس؟
الفائدة الكبرى هي فك الارتباط الوثيق بين أجزاء النظام، مما يجعل الكود سهل التعديل والصيانة، ويتيح اختبار الكلاسات آلياً (Unit Testing) بنسبة 100% عبر كائنات وهمية (Mocks).
3. لماذا يفضل Constructor Injection على Setter Injection؟
لأن Constructor Injection يضمن أن الكائن لا يمكن إنشاؤه إطلاقاً بدون توفير كافة الخدمات الأساسية التي يحتاجها ليعمل، مما يمنع حدوث أخطاء Null Pointer أو تشغيل كائنات في حالة غير مكتملة.
4. ما هو الفرق بين Dependency Injection ونمط Service Locator؟
في الـ DI النقي يتلقى الكلاس الخدمة المحددة التي يحتاجها صراحة في المنشئ، أما في الـ Service Locator فيتلقى الكلاس كائن الحاوية بالكامل ويبحث بداخله، مما يخفي اعتماداته الحقيقية مرة أخرى.
5. هل يعني استخدام DI الامتناع تماماً عن استخدام كلمة new في PHP؟
لا، كلمة new مسموح بها ومطلوبة لإنشاء كائنات البيانات البسيطة (Data Transfer Objects – DTOs) أو كائنات القيمة (Value Objects)، ولكنها محظورة داخل منطق الأعمال لإنشاء الخدمات التي تتواصل مع قواعد البيانات أو الـ APIs الخارجية.
6. كيف أتعامل مع المتغير العام $wpdb باستخدام Dependency Injection؟
تمرر كائن قاعدة البيانات كمعامل محقون في منشئ كلاس المستودع: public function __construct(private wpdb $db)، وتمرر له $GLOBALS['wpdb'] عند تهيئة الإضافة في ملف الإقلاع.
7. ما هو الكائن الوهمي (Mock Object) وكيف يرتبط بالـ DI؟
هو كائن تجريبي مزيف ينفذ نفس واجهة الخدمة الحقيقية، نستخدمه أثناء الـ Unit Testing لمحاكاة استجابات قواعد البيانات أو بوابات الدفع دون تنفيذ أي عمليات حقيقية على السيرفر.
8. ماذا أفعل إذا كان الكلاس يستقبل أكثر من 7 اعتمادات في المنشئ؟
هذا يسمى معمارياً “Constructor Over-injection” وهو جرس إنذار يفيد بأن الكلاس يفعل أشياء كثيرة جداً، ويجب تفكيكه إلى كلاسات أصغر متخصصة أو تجميع بعض الاعتمادات المرتبطة في كائن وسيط.
9. هل يتطلب استخدام Dependency Injection تثبيت مكتبات خارجية ضخمة؟
لا إطلاقاً، يمكنك تطبيق الـ Pure Dependency Injection يدوياً في كودك بمجرد تمرير الكائنات في الـ Constructors دون الحاجة لأي مكتبة أو إطار عمل خارجي.
10. كيف يؤثر حقن التبعيات على سرعة استجابة الموقع وأدائه؟
لا يوجد أي تأثير سلبي على الأداء؛ فتمرير مراجع الكائنات (Object References) في PHP سريع جداً ويستهلك ميكروثوانٍ معدودة لا تؤثر إطلاقاً على زمن استجابة الخادم.
11. ما هي ميزة Constructor Property Promotion في PHP 8؟
تتيح لك دمج تعريف المتغيرات وتحديد نوعها وتعيينها في سطر واحد داخل معاملات دالة __construct، مما يختصر 70% من الكود التكراري لحقن التبعيات.
12. هل يمكن استخدام Dependency Injection مع هوكات ووردبريس (Actions/Filters)؟
نعم، تقوم بإنشاء كائن المشترك (Subscriber) وحقن الخدمات التي يحتاجها في منشئه، ثم تسجيل دواله في الهوكات باستخدام [$this, 'callbackMethod'].
13. كيف يساعد DI في تبديل بوابات الدفع في المتاجر الإلكترونية؟
بإنشاء واجهة موحدة PaymentGatewayInterface؛ حيث يمكنك حقن بوابة فوري أو باي موب أو سترايب في نفس خدمة معالجة الطلبات دون تعديل سطر واحد في كود معالجة الطلب نفسه.
14. ما هو مبدأ Inversion of Control (عكس التحكم) وعلاقته بالـ DI؟
عكس التحكم هو المفهوم الشامل الذي ينص على نقل التحكم في إنشاء دورة حياة الكائنات من داخل الكلاس نفسه إلى بيئة أو إطار خارجي، وحقن التبعيات هو التطبيق العملي الأبرز لهذا المبدأ.
15. كيف أتعامل مع التبعيات الاختيارية في الكلاس؟
تستخدم معها معاملات اختيارية في المنشئ بقيمة افتراضية null أو تستخدم دالة تعيين مخصصة (Setter Method) لفحص وجودها قبل الاستخدام.
16. لماذا يعتبر ربط الكود بالواجهات (Interfaces) أفضل من ربطه بكلاسات محددة؟
لأن الواجهة تمثل عقداً مجرداً يصف “ماذا يفعل الكائن” وليس “كيف يفعله”، مما يمنحك حرية استبدال طريقة التنفيذ بالكامل دون أن تتأثر بقية أجزاء النظام.
17. أين يتم تجميع وحقن الكائنات فعلياً في إضافة ووردبريس؟
في كلاس نواة الإضافة المركزي (Plugin Bootstrapper) أو داخل مزودي الخدمات (Service Providers) الذين يتم استدعاؤهم عند إطلاق هوك plugins_loaded.
18. كيف يساعد DI في تقليل الأخطاء عند العمل ضمن فريق برمجي كبير؟
يجعل متطلبات كل كلاس واضحة ومكشوفة تماماً لأي مطور؛ فلا مجال للمفاجآت أو استدعاء دوال خفية في قاعدة البيانات تسقط النظام فجأة.
19. هل يدعم ووردبريس حقن التبعيات بشكل أصلي في نواته القديمة؟
نواة ووردبريس القديمة بنيت بالأسلوب الإجرائي، ولكن المعمارية الحديثة للإضافات الاحترافية تعتمد الـ DI بالكامل في طبقاتها الخاصة وتتعامل مع ووردبريس عبر طبقة محولات نظيفة (Adapters).
20. ما هي الحكمة الذهبية التي يجب أن يضعها كل مطور ووردبريس نصب عينيه؟
“لا تصنع أدواتك داخل ورشتك المغلقة؛ اطلب ما تحتاجه في باب الدخول، واجعل كودك مرناً يقبل التغيير ويرحب بالاختبارات، فهذا هو الفارق بين الكود الهش والبرمجيات المتينة!”
التوقيت السليم لربط وحقن التبعيات داخل دورة حياة ووردبريس
من القواعد المهمة جداً في بيئة ووردبريس هي عدم التسرع في بناء الكائنات وحقنها قبل اكتمال تحميل بيئة النظام. أفضل مكان لتجميع شجرة التبعيات وحقن الكائنات هو داخل هوك plugins_loaded أو init. هذا التوقيت يضمن أن كافة الإضافات المساعدة، وتعريفات الجداول، وصلاحيات المستخدمين، وإعدادات اللغة أصبحت جاهزة تماماً في الذاكرة قبل أن تبدأ خدماتك في العمل.
المصادر والمراجع الرسمية
- Martin Fowler: Inversion of Control Containers and the Dependency Injection Pattern
- PHP-FIG: PSR-11 Container Interface Standard
- PHP: The Right Way – Dependency Injection Principles
- WordPress Developer Handbook: Object-Oriented Programming & Clean Architecture
- PHPUnit Manual: Test Doubles (Mock Objects & Stubs)


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