حقن التبعية في PHP: عمليًا
\n\nحقن التبعية (DI) — أحد المبادئ الأساسية لـ SOLID، والذي يسمح بكتابة كود مرن وقابل للاختبار والصيانة. في هذه المقالة، سنشرح كيفية تنفيذ حقن التبعية في PHP بأمثلة واقعية، دون نظريات مجردة. ستتعلم كيفية التخلص من الروابط الصلبة بين الكلاسات وجعل كودك جاهزًا للتغييرات.
\n\nما هو حقن التبعية ولماذا هو ضروري؟
\n\nتخيل أن لديك كلاس UserController يعمل مع قاعدة البيانات. بدون DI، غالبًا ما تقوم بإنشاء كائن الاتصال مباشرة داخل المتحكم:
class UserController {\n private $db;\n public function __construct() {\n $this->db = new MySQLConnection('localhost', 'user', 'pass');\n }\n}\n\nهذا الأسلوب يُنشئ رابطًا صلبًا (tight coupling). إذا أردت التبديل إلى PostgreSQL أو استخدام كائن وهمي للاختبارات، ستحتاج إلى تغيير كود المتحكم. يحل حقن التبعية هذه المشكلة: يتم تمرير التبعيات من الخارج، بدلاً من إنشائها داخل الكلاس.
\n\nالمزايا الرئيسية لـ DI:
\n- \n
- تقليل ترابط الكود \n
- تبسيط الاختبار (سهولة استبدال التبعيات) \n
- إعادة استخدام الكلاسات \n
- هندسة نظيفة والالتزام بمبدأ عكس التبعية (DIP) \n
طرق حقن التبعية في PHP
\n\nهناك ثلاث طرق رئيسية لحقن التبعيات. سنستعرض كل منها مع أمثلة.
\n\n1. الحقن عبر المُنشئ
\n\nالطريقة الأكثر شيوعًا والموصى بها. يتم تمرير التبعيات عبر بارامترات المُنشئ وتخزينها في خصائص الكلاس.
\n\n<?php\ninterface DatabaseInterface {\n public function query(string $sql): array;\n}\n\nclass MySQLDatabase implements DatabaseInterface {\n public function query(string $sql): array {\n // المنطق الفعلي للعمل مع MySQL\n return ['id' => 1, 'name' => 'John'];\n }\n}\n\nclass UserRepository {\n private DatabaseInterface $db;\n \n public function __construct(DatabaseInterface $db) {\n $this->db = $db;\n }\n \n public function getUser(int $id): array {\n return $this->db->query("SELECT * FROM users WHERE id = $id");\n }\n}\n\n// الاستخدام:\n$db = new MySQLDatabase();\n$repo = new UserRepository($db);\n$user = $repo->getUser(1);\n?>\n\nلاحظ: نحن نستخدم الواجهة DatabaseInterface. هذا يسمح باستبدال التنفيذ بسهولة — على سبيل المثال، باستخدام كائن اختبار وهمي.
2. الحقن عبر السيتيرات
\n\nتُستخدم عندما لا تكون التبعية ضرورية في مرحلة إنشاء الكائن أو قد تتغير أثناء العمل.
\n\n<?php\nclass MailService {\n private ?MailerInterface $mailer = null;\n \n public function setMailer(MailerInterface $mailer): void {\n $this->mailer = $mailer;\n }\n \n public function send(string $to, string $subject, string $body): void {\n if ($this->mailer === null) {\n throw new \\RuntimeException('Mailer not configured');\n }\n $this->mailer->send($to, $subject, $body);\n }\n}\n\n// الاستخدام:\n$mailService = new MailService();\n$mailService->setMailer(new SmtpMailer());\n$mailService->send('user@example.com', 'Hello', 'Body');\n?>\n\nعيب هذه الطريقة — لا تضمن أن التبعية سيتم تعيينها قبل استدعاء الدالة send(). لذلك، للتبعيات الإلزامية يُفضل استخدام المُنشئ.
3. الحقن عبر الدالة
\n\nيتم تمرير التبعية مباشرة إلى الدالة التي تحتاجها. مفيد للتبعيات النادرة أو السياقية.
\n\n<?php\nclass ReportGenerator {\n public function generate(DataFetcherInterface $fetcher, string $reportType): array {\n $data = $fetcher->