مقدمة إلى Dependency Injection في PHP
تطوير PHP الحديث لا يمكن تصوره بدون مبادئ التصميم الشيئي. أحد الأنماط الرئيسية التي تسمح ببناء تطبيقات مرنة وقابلة للاختبار والصيانة هو Dependency Injection (DI) أو حقن التبعيات.
جوهر النمط بسيط: بدلاً من إنشاء الكائنات التابعة داخل الفئة، نقوم بتمريرها (حقنها) من الخارج. هذا يغير بشكل جذري بنية التطبيق، مما يجعل الفئات مستقلة عن التطبيقات المحددة ويبسط اختبارها.
في هذه المقالة، سنشرح ما هو Dependency Injection في PHP، وما هي طرق تنفيذه، وكيفية استخدام حاويات DI لأتمتة العملية.
المشكلة: لماذا بدون DI يكون الأمر سيئًا؟
لنأخذ مثالاً نموذجياً. الفئة UserController مسؤولة عن معالجة الطلبات المتعلقة بالمستخدمين. تستخدم قاعدة البيانات من خلال الفئة MysqlDatabase:
<?php
class MysqlDatabase { public function query(string $sql): array { // ... الاتصال بـ MySQL وتنفيذ الاستعلام return []; }}
class UserController { private MysqlDatabase $database;
public function __construct() { // ربط صارم بتطبيق محدد $this->database = new MysqlDatabase(); }
public function listUsers(): array { return $this->database->query('SELECT * FROM users'); }}
$controller = new UserController();ما هي مشاكل هذا الكود؟
- الاقتران القوي:
UserControllerمرتبط بشكل صارم بـMysqlDatabase. إذا أردنا الانتقال إلى PostgreSQL أو استخدام تخزين ملفات، سيتعين علينا تغيير كود المتحكم. - صعوبة الاختبار: لا يمكنك استبدال قاعدة البيانات بكائن وهمي في اختبارات الوحدة. كل اختبار سيتصل بقاعدة بيانات حقيقية، مما يكون بطيئاً وغير مريح.
- انتهاك مبدأ المسؤولية الواحدة: لا ينبغي للمتحكم أن يعرف كيفية إنشاء اتصال بقاعدة البيانات. مهمته هي معالجة الطلبات.
حل هذه المشكلة هو تطبيق مبدأ عكس التحكم (IoC) واستخدام Dependency Injection.
طرق حقن التبعيات في PHP
هناك ثلاث طرق رئيسية لحقن التبعية في الفئة:
1. الحقن عبر المُنشئ (Constructor Injection)
الطريقة الأكثر شيوعاً والموصى بها. يتم تمرير التبعية عبر معامل المُنشئ وعادةً ما تُحفظ كخاصية خاصة للفئة.
<?php
interface DatabaseInterface { public function query(string $sql): array;}
class MysqlDatabase implements DatabaseInterface { public function query(string $sql): array { // تطبيق لـ MySQL return []; }}
class PostgresDatabase implements DatabaseInterface { public function query(string $sql): array { // تطبيق لـ PostgreSQL return []; }}
class UserController { private DatabaseInterface $database;
// يتم حقن التبعية عبر المُنشئ public function __construct(DatabaseInterface $database) { $this->database = $database; }
public function listUsers(): array { return $this->database->query('SELECT * FROM users'); }}
// الاستخدام$mysqlDb = new MysqlDatabase();$controller = new UserController($mysqlDb);
// ننتقل بسهولة إلى قاعدة بيانات أخرى$postgresDb = new PostgresDatabase();$controller2 = new UserController($postgresDb);المزايا: التبعية واضحة بشكل صريح، ويتم إنشاء الكائن في حالة صالحة (التبعية إلزامية).
2. الحقن عبر الدوال الضابطة (Setter Injection)
مناسب للتبعيات الاختيارية أو عندما تكون التبعيات