Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

حقن التبعية

جزء من قسم Object Oriented Programming في رحلة PHP على Coddy — الدرس 57 من 91.

في الدرس السابق، رأيت كيف يسمح التركيب (composition) لـ Car بأن تحتوي على كائن Engine. ولكن هناك مشكلة في إنشاء التبعيات داخل الفئة (class) - فهي تجعل الكود جامداً وصعب الاختبار. حقن التبعية (Dependency Injection) يحل هذه المشكلة عن طريق تمرير التبعيات من الخارج بدلاً من إنشائها داخلياً.

قارن بين هذين النهجين:

<?php
// بدون حقن التبعية (DI) - يتم إنشاء التبعية في الداخل
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // ارتباط وثيق (Tightly coupled)
    }
}

// مع حقن التبعية (DI) - يتم تمرير التبعية
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // تم حقنها من الخارج

النهج الثاني هو حقن التبعية (dependency injection). لا تقوم Car بإنشاء المحرك الخاص بها - بل تستقبله. هذا التغيير البسيط له فوائد قوية: يمكنك تمرير أنواع محركات مختلفة، وتبديل التنفيذات للاختبار، وتصبح الفئة أكثر مرونة.

لمرونة أكبر، قم بحقن الواجهات (interfaces) بدلاً من الفئات الملموسة (concrete classes):

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

الآن تعمل Car مع أي فئة تقوم بتنفيذ EngineInterface - محرك غاز، أو محرك كهربائي، أو كائن وهمي (mock) للاختبار. هذا الفصل (decoupling) هو السبب في أن حقن التبعية (dependency injection) أمر أساسي لكتابة تطبيقات PHP قابلة للصيانة والاختبار.

challenge icon

التحدي

سهل

لنقم ببناء نظام إشعارات يوضح قوة حقن التبعية (Dependency Injection). بدلاً من كتابة طريقة إرسال الرسائل بشكل ثابت (hardcoding)، ستنشئ نظاماً مرناً حيث يمكن تبديل طريقة التسليم بسهولة.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • MessageSenderInterface.php — قم بتعريف واجهة (interface) تسمى MessageSenderInterface مع طريقة واحدة send(string $recipient, string $message): string. تحدد هذه الواجهة العقد الذي يجب أن يتبعه أي مرسل رسائل.
  • EmailSender.php — أنشئ فئة EmailSender تقوم بتنفيذ MessageSenderInterface. قم بتضمين ملف الواجهة. يجب أن تعيد الطريقة send() القيمة "Email to $recipient: $message".
  • NotificationService.php — أنشئ فئة NotificationService تستقبل تبعياتها من خلال المنشئ (constructor). قم بتضمين ملف الواجهة. يجب على الفئة:
    • قبول MessageSenderInterface في المنشئ الخاص بها باستخدام خاصية constructor promotion.
    • امتلاك طريقة notify(string $recipient, string $message) تقوم بتفويض المهمة للمرسل المحقون وتعيد نتيجته.
    لاحظ كيف تعتمد NotificationService على الواجهة، وليس على فئة ملموسة (concrete class). هذا يعني أنه يمكنك حقن أي مرسل ينفذ الواجهة دون تغيير الخدمة.
  • main.php — قم بتضمين ملفي EmailSender و NotificationService. ستتلقى مدخلين: مستلم ورسالة. أنشئ كائناً من EmailSender، وقم بحقنه في NotificationService، ثم استدعِ notify() واطبع النتيجة.

هذا النمط يحافظ على مرونة NotificationService — فهي تعمل مع أي مرسل تقوم بحقنه، سواء كان بريداً إلكترونياً، أو رسائل SMS، أو أي شيء آخر لم تقم ببنائه بعد. الخدمة لا تنشئ تبعياتها الخاصة؛ بل تستقبلها من الخارج.

ورقة مرجعية

حقن التبعية (Dependency Injection - DI) هو أسلوب يتم فيه تمرير التبعيات إلى الفئة (class) من الخارج بدلاً من إنشائها داخلياً.

بدون حقن التبعية (ارتباط وثيق - tightly coupled):

<?php
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // تم إنشاؤها داخلياً
    }
}

مع حقن التبعية (ارتباط ضعيف - loosely coupled):

<?php
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // تم حقنها من الخارج

للحصول على أقصى قدر من المرونة، قم بحقن الواجهات (interfaces) بدلاً من الفئات الملموسة (concrete classes):

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

يتيح ذلك لـ Car العمل مع أي فئة تقوم بتنفيذ EngineInterface، مما يجعل الكود أكثر قابلية للصيانة والاختبار.

جرّب بنفسك

<?php
require_once 'EmailSender.php';
require_once 'NotificationService.php';

// قراءة المدخلات
$recipient = trim(fgets(STDIN));
$message = trim(fgets(STDIN));

// TODO: EmailSender إنشاء مثيل من
// TODO: NotificationService حقنه في
// TODO: وطباعة النتيجة notify() استدعاء

?>
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming