ماذا لو كان الكود يتحدث لغة عملك؟
\n\nتخيل: مدير المبيعات يكتب شرط الخصم بلغة إنجليزية واضحة، والنظام يحوّله تلقائيًا إلى كود عملي. أو محاسب يصف حساب الضريبة بعبارة "إذا كان المبلغ أكبر من 1000 والعميل من الاتحاد الأوروبي، طبق نسبة 5%"، دون كتابة سطر واحد من PHP. يبدو هذا كخيال علمي؟ مرحبًا بك في عالم اللغات المخصصة للمجال (DSL) بلغة PHP.
\n\nمعظم المطورين يعرفون PHP كلغة لتطوير الويب، لكن القليل منهم يستخدم قوتها الخارقة — بناء DSL. هذه ليست مجرد حيلة تقنية، بل أداة قوية تحوّل فوضى قواعد الأعمال إلى كود أنيق وقابل للقراءة. في هذا المقال، لن نتحدث عن النظرية فقط، بل سنبني DSL بلغة PHP عمليًا من الصفر. ستشاهد كيف يمكن تطوير DSL أن يبسط حياتك وحياة عملائك.
\n\nما هو DSL ولماذا تحتاجه في PHP؟
\n\nDSL (Domain Specific Language) هي لغة مصممة خصيصًا لمهمة محددة. على عكس لغات البرمجة العامة (مثل PHP أو Java أو Python)، تتحدث DSL لغة مجال عملك. أمثلة: SQL لقواعد البيانات، التعبيرات المنتظمة للبحث عن الأنماط، HTML لبناء الصفحات.
\n\nلماذا تحتاج إنشاء DSL بلغة PHP؟ ثلاثة أسباب رئيسية:
\n\n- \n
- الوضوح. كود DSL يبدو كتعليمات بالعربية أو الإنجليزية. لا حاجة لأن تكون مبرمجًا لفهم المنطق. \n
- الأمان. تتحكم في ما يمكن للمستخدم فعله. لا حاجة لاستخدام eval أو shell_exec. \n
- سرعة التطوير. يمكن لمحللي الأعمال والمديرين وصف القواعد بأنفسهم، دون إلهاء فريق التطوير. \n
في PHP، هناك طريقتان رئيسيتان لـ إنشاء لغات مخصصة للمجال: DSL داخلي (يُبنى على بناء جملة PHP) وDSL خارجي (يتطلب محللًا خاصًا به). سنبدأ بالأول — فهو أبسط ويغطي غالبًا 80% من المهام.
\n\nالطريقة 1: DSL داخلي — Fluent Interface وسحر الدوال
\n\nDSL الداخلي هو عندما تستخدم بناء جملة PHP ولكن تنظمه ليبدو كلغة طبيعية. الكلاسيكي هو fluent interface (الواجهة الانسيابية). لنكتب DSL لبناء استعلامات SQL، لتشعر بالسحر فورًا.
\n\nمثال: DSL لبناء استعلامات SQL
\n\nبدلاً من كتابة:
\n\n$query = "SELECT * FROM users WHERE age > 18 AND city = 'Moscow' ORDER BY name ASC LIMIT 10";\n\nنريد الكتابة بهذا الشكل:
\n\n$query = QueryBuilder::table('users')\n ->select('*')\n ->where('age', '>', 18)\n ->where('city', '=', 'Moscow')\n ->orderBy('name', 'ASC')\n ->limit(10)\n ->get();\n\nأوافقك الرأي، إنها تقرأ كجملة. التنفيذ بسيط: كل دالة تُرجع $this، وفي النهاية دالة get() تجمع النص.
\n\nclass QueryBuilder {\n private array $parts = [];\n\n public function table(string $name): self {\n $this->parts['table'] = $name;\n return $this;\n }\n\n public function select(string $columns = '*'): self {\n $this->parts['select'] = $columns;\n return $this;\n }\n\n public function where(string $column, string $operator, $value): self {\n $this->parts['where'][] = "$column $operator " . (is_string($value) ? "'$value'" : $value);\n return $this;\n }\n\n public function orderBy(string $column, string $direction = 'ASC'): self {\n $this->parts['order'] = "$column $direction";\n return $this;\n }\n\n public function limit(int $limit): self {\n $this->parts['limit'] = $limit;\n return $this;\n }\n\n public function get(): string {\n $sql = "SELECT {$this->parts['select']} FROM {$this->parts['table']}";\n if (!empty($this->parts['where'])) {\n $sql .= " WHERE " . implode(' AND