مقدمة: عندما يصمت Query Builder وتحتاج لرؤية SQL
\n\nهل سبق لك أن كتبت استعلامًا معقدًا عبر Query Builder، وقمت بتشغيله — ثم حصلت على خطأ أو بيانات غير صحيحة؟ تشعر بالحاجة لرؤية ما يولده سلسلة الدوال الخاصة بك بالضبط، لكن الإطار يعيد لك كائنًا وليس سلسلة نصية. موقف مألوف، أليس كذلك؟
\n\nهذا السؤال هو من أكثر الأسئلة شيوعًا على Stack Overflow (عشرات الآلاف من المشاهدات). وليس من دون سبب. إن القدرة على "استخراج" SQL الخام من Query Builder هي قوة خارقة توفر ساعات من التصحيح. خاصة عند العمل مع ORM مثل Doctrine أو Eloquent في Laravel، حيث تُبنى الاستعلامات عبر سلسلة من الدوال، ويكون SQL الحقيقي مخفيًا في الداخل.
\n\nفي هذه المقالة، سنشرح كيفية الحصول على سلسلة SQL الخام من Query Builder في أطر PHP المختلفة، وسنقدم أمثلة عملية ونوضح لماذا هذا مهم لكل مطور. هيا بنا!
\n\n1. لماذا قد تحتاج إلى SQL خام؟
\n\nقبل أن نغوص في الكود، دعنا نفهم لماذا يجب أن ننظر إلى SQL "الخام" إذا كان Query Builder يفعل كل شيء نيابة عنا.
\n\n- \n
- تصحيح الأخطاء. عندما لا يعمل الاستعلام، ترى فقط كائنًا. أما SQL فهو الحقيقة. سترى أي الجداول يتم ربطها، وأي شروط WHERE تُطبق، وأين يوجد خطأ في الصياغة. \n
- تحسين الأداء. قد يُنشئ Query Builder استعلامًا غير فعال. بالنظر إليه، ستلاحظ وجود JOINs زائدة أو عدم وجود فهارس. \n
- التعلم. إذا كنت مبتدئًا، فإن مشاهدة SQL المُنشأة تساعدك على فهم كيف يترجم ORM دوال PHP الخاصة بك إلى أوامر SQL حقيقية. \n
- التصدير والتسجيل. أحيانًا تحتاج إلى تسجيل الاستعلام في سجل أو إرساله إلى خدمة أخرى. بدون سلسلة نصية، هذا مستحيل. \n
الآن، دعنا ننتقل إلى الجزء الأكثر إثارة — الكود.
\n\n2. Laravel Eloquent: toSql() و getBindings()
\n\nLaravel هو إطار PHP الأكثر شهرة، ويوفر Query Builder الخاص به (جزء من Eloquent) دوالًا مدمجة للحصول على SQL. إليك كيف يعمل ذلك.
\n\nمثال بسيط: toSql()
\n\nuse Illuminate\\Support\\Facades\\DB;\n\n$query = DB::table('users')\n ->where('age', '>', 18)\n ->where('status', 'active')\n ->orderBy('name', 'asc');\n\n$sql = $query->toSql();\n\necho $sql;\n// الإخراج: select * from `users` where `age` > ? and `status` = ? order by `name` asc\n\nممتاز! لكن هل لاحظت علامات الاستفهام؟ هذه هي العناصر النائبة للاستعلامات المُعدة. لرؤية القيم الحقيقية، تحتاج إلى استخدام getBindings().
SQL كامل مع استبدال القيم
\n\n$bindings = $query->getBindings();\n\n$fullSql = vsprintf(str_replace('?', '%s', $sql), array_map(function ($value) {\n return is_numeric($value) ? $value : "'" . $value . "'";\n}, $bindings));\n\necho $fullSql;\n// الإخراج: select * from `users` where `age` > 18 and `status` = 'active' order by `name` asc\n\nهذه الخدعة تستبدل العناصر النائبة بالقيم الحقيقية. لكن كن حذرًا: لا تستخدم هذه الطريقة لتنفيذ الاستعلام — فهي لا تعقم البيانات، وهي فقط لأغراض التصحيح.
\n\nطريقة dd() للتصحيح السريع
\n\nفي Laravel، هناك طريقة مريحة تسمى dd() (dump and die) تعرض SQL والروابط:
DB::table('users')->where('age', '>', 18)->dd();\n// ستوقف التنفيذ وتظهر:\n// sql: select * from `users` where `age` > ?\n// bindings: [18]\n\nنصيحة: استخدم dd() فقط في بيئة التطوير. في الإنتاج، سيتسبب في إيقاف الاستعلام.
3. Doctrine ORM: getSQL() والمعاملات
\n\nDoctrine هو ORM قوي يُستخدم في Symfony ومشاريع أخرى. يختلف Query Builder الخاص به قليلاً، لكن المبدأ واحد.
\n\nمثال أساسي
\n\nuse Doctrine\\ORM\\EntityManagerInterface;\n\n// لنفترض أن $entityManager تم الحصول عليه عبر DI\n$queryBuilder = $entityManager->createQueryBuil