Generics في PHP: دليل شامل للكتابة مع أمثلة

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

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

ابدأ الدورة

لماذا Generics ليست مجرد موضة عابرة، بل ضرورة؟

\n\n

إذا كنت قد كتبت يومًا كود PHP يعمل مع المجموعات، المستودعات أو طبقة الخدمات، فبالتأكيد واجهت موقفًا: الدالة تستقبل array، لكن ما بداخلها لغز. قد يكون هناك مستخدمون، أو ربما قطط. أو الأسوأ من ذلك — الاثنان معًا. هنا تأتي Generics (الجينيريكس) للإنقاذ — أداة قوية للكتابة تجعل كودك قابلًا للتوقع، آمنًا وموثقًا ذاتيًا.

\n\n

في هذه المقالة، سنشرح كيف تعمل generics في PHP، ولماذا تحتاجها وكيف تستخدمها في المشاريع الحقيقية. ستتعلم كيف تتجنب الأخطاء النمطية وتجعل كودك احترافيًا. هيا بنا!

\n\n

ما هي Generics بكلمات بسيطة؟

\n\n

تخيل أنك مطور متجر إلكتروني. لديك كلاس Cart يخزن المنتجات. بدون generics تكتب:

\n\n
class Cart {\n    private array $items;\n    \n    public function addItem($item): void {\n        $this->items[] = $item;\n    }\n    \n    public function getItems(): array {\n        return $this->items;\n    }\n}
\n\n

المشكلة؟ $item يمكن أن يكون أي شيء — رقمًا، نصًا، كائن Product أو حتى null. IDE لن تخبرك بنوع القيمة المرجعة من getItems(). سيتعين عليك العودة إلى التوثيق أو التصحيح.

\n\n

Generics تحل هذه المشكلة. تسمح لك بتحديد: "مرحبًا، هذا الكلاس يعمل فقط مع المنتجات!". يبدو الأمر هكذا:

\n\n
class Cart {\n    /** @param Product[] $items */\n    private array $items;\n    \n    public function addItem(Product $item): void {\n        $this->items[] = $item;\n    }\n    \n    /** @return Product[] */\n    public function getItems(): array {\n        return $this->items;\n    }\n}
\n\n

لكن هذا مجرد تقليد. ظهرت generics الحقيقية في PHP مع PHP 8.0، والدعم الكامل في PHP 8.1+ باستخدام السمات (attributes) والتحليل الثابت (مثل PHPStan أو Psalm).

\n\n

كيف تعمل Generics في PHP فعليًا؟

\n\n

على عكس Java أو C#، لا يمتلك PHP دعمًا أصليًا لـ generics على مستوى الصياغة (حتى الآن). لكن بفضل تعليقات PHPDoc وأدوات التحليل الثابت يمكننا الحصول على جميع مزايا الكتابة.

\n\n

أنواع القوالب (Template Types)

\n\n

النهج الأكثر شيوعًا هو استخدام @template في PHPDoc. لنلق نظرة على مثال مع مجموعة:

\n\n
/**\n * @template T\n */\nclass Collection {\n    /** @var array<int, T> */\n    private array $items = [];\n    \n    /**\n     * @param T $item\n     */\n    public function add(mixed $item): void {\n        $this->items[] = $item;\n    }\n    \n    /**\n     * @return T\n     */\n    public function get(int $index): mixed {\n        return $this->items[$index] ?? throw new \\OutOfBoundsException();\n    }\n}
\n\n

الآن عند استخدام هذا الكلاس، تحدد النوع المحدد:

\n\n
/** @var Collection<User> $userCollection */\n$userCollection = new Collection();\n$userCollection->add(new User('Alice'));\n$user = $userCollection->get(0); // PHPStan يعرف: $user هو User\necho $user->name; // آمن!
\n\n

القيود (Constraints) لـ Generics

\n\n

أحيانًا تحتاج أن النوع T ليس أي نوع، بل يجب أن ينفذ واجهة معينة. هذا يُسمى bound (تقييد):

\n\n
/**\n * @template T of \\JsonSerializable\n */\nclass JsonCollection {\n    /** @var array<int, T> */\n    private array $items = [];\n    \n    public function toJson(): string {\n        return json_encode($this->items);\n    }\n}
\n\n

الآن لن تتمكن من إضافة كائن لا ينفذ JsonSerializable إلى المجموعة. IDE والمحلل الثابت سيظهران خطأ.

\n\n

أمثلة عملية: من البسيط إلى المعقد<

المدونات

توصيات الكتب