„Doktor, mein Query Builder generiert etwas Seltsames…“
\nEine vertraute Situation: Sie schreiben eine elegante verkettete Abfrage mit dem Query Builder, sind überzeugt, dass alles perfekt funktioniert, aber am Ende erhalten Sie entweder einen Fehler oder die falschen Daten. Das Erste, was jedem PHP-Entwickler in den Sinn kommt: „Zeig mir, was du da eigentlich generierst!“.
\nDie Frage „How do I get the query builder to output its raw SQL query as a string?“ ist eine der beliebtesten auf Stack Overflow (tausende Aufrufe und Dutzende Antworten). Und das ist kein Zufall. Die Fähigkeit, schnell reines SQL zu erhalten, ist eine Superkraft, die Stunden der Fehlersuche spart.
\nIn diesem Artikel behandeln wir 3 funktionierende Methoden für PHP-Frameworks (Laravel, Doctrine, CakePHP), zeigen echte Codebeispiele und erklären, wann welche Methode verwendet werden sollte. Los geht‘s!
\n\nWarum wird das überhaupt benötigt? (Kurz und bündig)
\n- \n
- Fehlersuche – verstehen, warum die Abfrage nicht die richtigen Daten zurückgibt. \n
- Optimierung – sehen, welche JOINs und WHEREs tatsächlich ausgeführt werden. \n
- Lernen – Anfänger verstehen oft nicht, wie der Query Builder Methoden in SQL übersetzt. \n
- Protokollierung – die rohe Abfrage für Audits oder Wiederholungen speichern. \n
Bereit? Dann gehen wir zum Code über!
\n\nMethode 1: Laravel Eloquent / Query Builder – der Klassiker
\nIn Laravel gibt es mehrere Möglichkeiten, aber die zuverlässigste ist die Verwendung von toSql() zusammen mit getBindings(). Warum zusammen? Weil toSql() die Abfrage mit Platzhaltern ? anzeigt, und ohne das Einsetzen der Werte ist das nutzlos.
Einfaches Beispiel: SELECT mit 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 mit Platzhaltern abrufen\n$sql = $query->toSql();\n$bindings = $query->getBindings();\n\n// Alles in eine lesbare Form bringen\n$fullSql = vsprintf(str_replace('?', '%s', $sql), $bindings);\n\necho $fullSql;\n// Ergebnis:\n// SELECT * FROM users WHERE email LIKE '%@gmail.com' AND active = 1 ORDER BY created_at DESC\nWichtig: vsprintf ist kein Allheilmittel. Für komplexe Datentypen (z. B. NULL oder Arrays) verwenden Sie besser Str::replaceArray() oder das Paket laravel-query-builder-debug.
Fortgeschrittene Variante: mit join und 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// Schön formatieren\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\nTipp: Verwenden Sie preg_replace_array() – es ist sicherer als vsprintf, besonders wenn die Daten das Zeichen % enthalten.
Methode 2: Doctrine DBAL – für diejenigen, die Strenge lieben
\nDer Doctrine Query Builder (häufig in Symfony verwendet) ist ein etwas komplexeres Tier. Es gibt kein fertiges toSql(), aber es gibt getSQL() und getParameters(). Außerdem müssen die Parametertypen berücksichtigt werden.
Basisbeispiel
\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