"دكتور، Query Builder الخاص بي يولد شيئًا غريبًا..."
\nموقف مألوف: تكتب استعلامًا متسلسلًا أنيقًا باستخدام Query Builder، وتكون متأكدًا أن كل شيء يعمل بشكل مثالي، ولكن الناتج إما خطأ أو بيانات غير صحيحة. أول ما يتبادر إلى ذهن أي مطور PHP: "أرني ما الذي تولده بالفعل!"
\nالسؤال "How do I get the query builder to output its raw SQL query as a string?" هو أحد أكثر الأسئلة شيوعًا على Stack Overflow (آلاف المشاهدات وعشرات الإجابات). وهذا ليس من قبيل الصدفة. القدرة على الحصول بسرعة على SQL نقي هي قوة خارقة توفر ساعات من التصحيح.
\nفي هذه المقالة، سنشرح 3 طرق عملية لأطر PHP (Laravel، Doctrine، CakePHP)، وسنعرض أمثلة كود حقيقية ونشرح متى نستخدم كل طريقة. هيا بنا!
\n\nلماذا نحتاج هذا؟ (باختصار وبشكل مباشر)
\n- \n
- التصحيح — لفهم سبب عدم إرجاع الاستعلام للبيانات الصحيحة. \n
- تحسين الأداء — لرؤية JOINs و WHEREs التي يتم تنفيذها فعليًا. \n
- التعلم — المبتدئون غالبًا لا يفهمون كيف يترجم Query Builder الطرق إلى SQL. \n
- تسجيل الأحداث — لحفظ الاستعلام الخام للتدقيق أو إعادة التشغيل. \n
مستعدون؟ إذن ننتقل إلى الكود!
\n\nالطريقة 1: Laravel Eloquent / Query Builder — الطريقة الكلاسيكية
\nفي Laravel، هناك عدة طرق، لكن الأكثر موثوقية هو استخدام toSql() مع getBindings(). لماذا معًا؟ لأن toSql() يعرض الاستعلام مع العناصر النائبة ?، وبدون استبدال القيم يكون عديم الفائدة.
مثال بسيط: SELECT مع WHERE
\nuse App\\Models\\User;\n\n$query = User::where('email', 'like', '%@gmail.com')\n ->where('active', 1)\n ->orderBy('created_at', 'desc');\n\n// الحصول على SQL مع العناصر النائبة\n$sql = $query->toSql();\n$bindings = $query->getBindings();\n\n// تجميع كل شيء في شكل قابل للقراءة\n$fullSql = vsprintf(str_replace('?', '%s', $sql), $bindings);\n\necho $fullSql;\n// النتيجة:\n// SELECT * FROM users WHERE email LIKE '%@gmail.com' AND active = 1 ORDER BY created_at DESC\nهام: vsprintf ليس حلاً سحريًا. لأنواع البيانات المعقدة (مثل NULL أو المصفوفات) من الأفضل استخدام Str::replaceArray() أو الحزمة laravel-query-builder-debug.
نسخة متقدمة: مع join و whereIn
\n$query = DB::table('orders')\n ->join('users', 'orders.user_id', '=', 'users.id')\n ->whereIn('orders.status', ['pending', 'processing'])\n ->where('orders.total', '>', 100)\n ->select('orders.*', 'users.name');\n\n$bindings = $query->getBindings();\n$sql = $query->toSql();\n\n// تنسيق جميل\n$fullSql = preg_replace_array('/\\?/', $bindings, $sql);\necho $fullSql;\n// SELECT orders.*, users.name FROM orders INNER JOIN users ON orders.user_id = users.id WHERE orders.status IN ('pending', 'processing') AND orders.total > 100\nنصيحة: استخدم preg_replace_array() — فهو أكثر أمانًا من vsprintf، خاصة عندما تحتوي البيانات على الرمز %.
الطريقة 2: Doctrine DBAL — لمن يحبون الدقة
\nDoctrine Query Builder (يُستخدم غالبًا في Symfony) هو وحش أكثر تعقيدًا. لا يوجد هنا toSql() جاهز، ولكن يوجد getSQL() و getParameters(). بالإضافة إلى ذلك، يجب مراعاة أنواع المعاملات.
مثال أساسي
\nuse Doctrine\\DBAL\\Connection;\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\n\n/** @var Connection $connection */\n$queryBuilder = $connection->createQueryBuilder();\n$queryBuilder\n ->select('p.*', 'c.name as category_name')\n ->from('products', 'p')\n ->join('p', 'categories', 'c', 'p.category_id = c.id')\n ->where('p.price > :minPrice')\n ->andWhere('p.sto