مقدمة: لماذا يحلم مطورو PHP بالجينيريكس
\n\nإذا كنت قد كتبت يومًا بلغة Java أو C# أو TypeScript، ثم عدت إلى PHP، فبالتأكيد شعرت بخيبة أمل طفيفة. في تلك اللغات يوجد نظام أنيق من الأنواع العامة (Generics)، والذي يسمح بإنشاء هياكل بيانات مرنة وآمنة من حيث الأنواع. أما في PHP، فقد سادت لفترة طويلة "فوضى التنميط": يمكن للمصفوفات أن تحتوي على أي شيء، وكانت بيئة التطوير المتكاملة (IDE) تبرز فقط الأخطاء الأكثر وضوحًا.
\n\nلكن الأوقات تتغير. بدءًا من PHP 7.0 وخاصة في عصر PHP 8.0/8.1/8.2، وجد المجتمع طريقة لاستخدام Generics في PHP. نعم، على مستوى بناء الجملة للغة لا تزال غير موجودة (وعلى الأرجح لن تكون في السنوات القادمة)، لكن هذا لا يعني أنه لا يمكنك الاستفادة من جميع مزايا البرمجة العامة اليوم.
\n\nفي هذه المقالة، سنشرح كيف يعمل التنميط في PHP مع Generics، وما هي الأدوات الموجودة لذلك (PHPStan, Psalm)، وكيفية كتابة كود يكون مرنًا وآمنًا في نفس الوقت. استعدوا - سيكون ساخنًا!
\n\nما هي Generics ولماذا نحتاجها في PHP؟
\n\nGenerics (الأنواع العامة) هي آلية تسمح بتمييز الفئات والواجهات والدوال بالأنواع. بدلاً من كتابة تطبيق منفصل لكل نوع بيانات، تقوم بإنشاء تطبيق واحد عالمي، ثم تقوم بتعيين النوع المحدد عند الاستخدام.
\n\nتخيل أنك تكتب فئة لطابور (Queue). بدون الجينيريكس، يمكنك تخزين نوع بيانات واحد فقط أو استخدام mixed (مما يقتل التنميط بالكامل). مع Generics، يمكنك القول: "هذا طابور من الأعداد الصحيحة" أو "هذا طابور من كائنات User".
مشكلة التنميط "الخام"
\n\nانظر إلى كود PHP النموذجي هذا:
\n\n<?php\n\nclass Collection {\n private array $items = [];\n\n public function add($item): void {\n $this->items[] = $item;\n }\n\n public function get(int $index) {\n return $this->items[$index] ?? null;\n }\n}\n\n$users = new Collection();\n$users->add(new User('Alice'));\n$users->add(new User('Bob'));\n$users->add('هذه سلسلة نصية وليست مستخدمًا'); // خطأ! لكن PHP سيبقى صامتًا\n\n$user = $users->get(2); // سيعيد سلسلة نصية، IDE لن يشتبه في شيء\n$user->getName(); // خطأ فادح أثناء التنفيذ\n\n\nهذه هي المشكلة الكلاسيكية لمطور PHP: غياب التحكم في الأنواع في مرحلة كتابة الكود. تكتشف الخطأ فقط في وقت التشغيل (runtime)، عندما يكون المستخدم قد ضغط بالفعل على الزر. Generics تحل هذه المشكلة من جذورها.
\n\nGenerics في PHP: كيف تعمل عمليًا
\n\nنظرًا لعدم وجود بناء جملة أصلي لـ Generics في PHP، نستخدم التعليقات التوضيحية في PHPDoc، والتي تتم معالجتها بواسطة محللين ثابتين - PHPStan و Psalm. تتحقق هذه الأدوات من الكود للتأكد من مطابقته للأنواع المعلنة، مما يحاكي عمل الجينيريكس فعليًا.
\n\nبناء الجملة الأساسي: الأنواع القالبية
\n\nلنفترض أننا نريد جعل مجموعتنا آمنة من حيث الأنواع. إليك كيف يبدو ذلك باستخدام Generics عبر PHPDoc:
\n\n<?php\n\n/**\n * @template T\n */\nclass GenericCollection {\n /** @var array<int, T> */\n private array $items = [];\n\n /**\n * @param T $item\n */\n public function add($item): void {\n $this->items[] = $item;\n }\n\n /**\n * @param int $index\n * @return T|null\n */\n public function get(int $index) {\n return $this->items[$index] ?? null;\n }\n}\n\n// الآن نستخدم:\n/** @var GenericCollection<User> $users */\n$users = new GenericCollection();\n$users->add(new User('Alice'));\n$users->add('سلسلة نصية'); // PHPStan سيعطي خطأ: Expected type User, got string\n\n$user = $users->get(0); // PHPStan يعرف أن هذا User\necho $user->getName(); //