Generics في PHP: لماذا هي ضرورية وكيفية استخدامها
الأنواع العامة (Generics) هي واحدة من أكثر الميزات المنتظرة والمثيرة للنقاش في عالم PHP. على الرغم من أنها لم تُنفَّذ بالكامل رسميًا في اللغة حتى PHP 8.x، إلا أن المجتمع يستخدم بنشاط أساليب بديلة وينتظر الدعم الكامل. في هذه المقالة، سنشرح لماذا نحتاج إلى Generics في PHP، وكيف يمكن محاكاتها، وما المزايا التي تقدمها للمطور.
ما هي Generics ولماذا هي ضرورية؟
تسمح Generics (الأنواع العامة) بإنشاء كلاسات وواجهات ودوال تعمل مع أنواع بيانات مختلفة مع الحفاظ على الكتابة الصارمة (strict typing). في لغات البرمجة ذات الكتابة الثابتة (Java, C#, TypeScript) أصبحت معيارًا منذ فترة طويلة. بالنسبة لـ PHP، التي كانت في الأصل ديناميكية الكتابة، فإن Generics هي خطوة نحو كود أكثر موثوقية وقابلية للقراءة.
المشكلات الرئيسية التي تحلها Generics:
- أمان الأنواع — تمنع الأخطاء المتعلقة بتمرير بيانات من نوع خاطئ.
- تقليل تكرار الكود — ليست هناك حاجة لكتابة كلاسات منفصلة لكل نوع.
- تحسين قابلية القراءة — يصبح من الواضح فورًا أي الأنواع تعمل معها الدالة أو الكلاس.
- دعم بيئات التطوير (IDE) — يصبح الإكمال التلقائي وتحليل الكود أكثر دقة.
المشكلة بدون Generics: مثال في PHP
تخيل أنك بحاجة لإنشاء مجموعة (Collection) تخزن الأعداد الصحيحة فقط. بدون Generics يمكنك كتابة:
class IntegerCollection { private array $items = [];
public function add(int $item): void { $this->items[] = $item; }
public function get(int $index): int { return $this->items[$index]; }}كل شيء يعمل، ولكن إذا احتجت مجموعة للنصوص (strings)، فسيتعين عليك نسخ الكود وتغيير الأنواع. وإذا احتجت مجموعة للكائنات (objects) — نفس الشيء. هذا يؤدي إلى التكرار وزيادة حجم الكود.
محاكاة Generics في PHP باستخدام التعليقات التوضيحية و PHPDoc
حاليًا، لا يدعم PHP Generics كاملة على مستوى بناء الجملة، ولكن يمكن محاكاتها باستخدام تعليقات PHPDoc التوضيحية والمكتبات الخاصة (مثل phpstan/phpstan أو vimeo/psalm).
مثال لكلاس عام مع التعليقات التوضيحية:
/** * @template T */class Collection { private array $items = [];
/** * @param T $item */ public function add($item): void { $this->items[] = $item; }
/** * @return T */ public function get(int $index) { return $this->items[$index]; }}الآن عند استخدام الكلاس، يمكنك تحديد النوع المحدد:
/** @var Collection<int> $intCollection */$intCollection = new Collection();$intCollection->add(42);$value = $intCollection->get(0); // IDE يعرف أن هذا intالمحللات الثابتة (PHPStan, Psalm) ستتحقق من أنك لا تمرر نصًا (string) إلى مجموعة الأعداد الصحيحة. هذا يوفر نفس مستوى الأمان تقريبًا مثل Generics الكاملة.
أمثلة حقيقية لاستخدام Generics في PHP
لننظر في عدة سيناريوهات عملية حيث تكون Generics مفيدة بشكل خاص.
1. المستودعات (Repositories) في Doctrine
في Doctrine ORM، غالبًا ما تعمل مع المستودعات. تساعد Generics في تحديد الكيان (entity) الذي يعمل معه المستودع بشكل صريح:
/** * @template T of object */interface RepositoryInterface { /** * @param int $id * @return T|null */ public function find(int $id): ?object;}
/** * @implements RepositoryInterface<User> */class UserRepository implements RepositoryInterface { public function find(int $id): ?User { // منطق البحث }}2. نمط "الإستراتيجية" (Strategy) مع الأنواع العامة
تسمح Generics بإنشاء