Generics في PHP: التنميط الذي كنت تفتقده

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا يحلم مطورو 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\n

Generics (الأنواع العامة) هي آلية تسمح بتمييز الفئات والواجهات والدوال بالأنواع. بدلاً من كتابة تطبيق منفصل لكل نوع بيانات، تقوم بإنشاء تطبيق واحد عالمي، ثم تقوم بتعيين النوع المحدد عند الاستخدام.

\n\n

تخيل أنك تكتب فئة لطابور (Queue). بدون الجينيريكس، يمكنك تخزين نوع بيانات واحد فقط أو استخدام mixed (مما يقتل التنميط بالكامل). مع Generics، يمكنك القول: "هذا طابور من الأعداد الصحيحة" أو "هذا طابور من كائنات User".

\n\n

مشكلة التنميط "الخام"

\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\n

Generics في 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(); //                

المدونات

توصيات الكتب