In Laravel, what do ->dump(), ->dd() and ->ddRawSql() print when chained on a collection or a query builder?
answer
- collections dump their items
- builders dump SQL, not results
- placeholders plus a bindings array
- RawSql substitutes the bindings
- toRawSql() returns a string
basics
~20 sOn a collection they dump its items; ->dump() returns the collection so the chain continues, ->dd() stops. On a query builder they dump the SQL, not results: ->dump() shows ? placeholders plus bindings, ->dumpRawSql() and ->ddRawSql() show the SQL with bindings substituted.
solid answer
~40 sA collection's `->dump()` calls `dump($this->all())` and returns the same collection, so you can drop it between `filter()` and `sum()` to watch data change; `->dd()` dumps the items and ends the script. On a query builder the methods dump the **query, not its results**, and nothing is executed: `->dump()` prints `toSql()` with `?` placeholders plus the bindings array and returns the builder, `->dd()` prints the same and stops, while `->dumpRawSql()` and `->ddRawSql()` print `toRawSql()`, the SQL with bindings substituted, ready to paste into a database client. Eloquent forwards all four to the underlying query builder with global scopes applied, so the dump shows scope conditions too. When you need the SQL in a log instead, `toRawSql()` returns it as a string.
code
php · 16 lines<?php
use Illuminate\Support\Facades\DB;
DB::table('promotions')
->where('active', true)
->where('category_id', 7)
->dump();
// "select * from "promotions" where "active" = ? and "category_id" = ?"
// [true, 7]
DB::table('promotions')
->where('active', true)
->where('category_id', 7)
->ddRawSql();
// select * from "promotions" where "active" = 1 and "category_id" = 7go deeper
Recall that collections and query builders have ->dump() and ->dd(), and that on a query they print SQL rather than results.
Explain placeholders plus bindings versus dumpRawSql, what each method returns, and why toRawSql suits logging.
Point out the Eloquent passthru return type, global scopes in dumps, and the LazyCollection cost of dumping mid-pipeline.
Decide which query-inspection tools belong in everyday workflow versus tests and logs, so SQL debugging does not leak into shipped code.
## Chainable dumps Laravel adds dump methods to the objects you most often chain, so you can inspect a value mid-chain without breaking it into variables. The global `dump()` and `dd()` helpers underneath are the same Symfony VarDumper helpers, so the output format and the "dd stops the script" rule are identical. | Called on | `->dump()` prints | `->dump()` returns | `->dd()` | |---|---|---|---| | `Collection` / `LazyCollection` | `$this->all()`, the items | the same collection | prints items, ends the script | | Query builder (`DB::table()`) | SQL with `?` and the bindings array | the builder | prints SQL and bindings, ends | | Eloquent builder (`Product::where()`) | forwarded to the base query builder | the **base** query builder | forwarded, ends | Other everyday objects have the same pair: `Str::of()` stringables, `Carbon` dates, the request (`$request->dump('coupon')`), validated input, and HTTP client requests and responses. ## Collections: watching data change For the grocery discount bug, a collection pipeline might compute the discount per item: ```php $discount = $cart->items ->filter(fn ($item) => $item->product->on_sale) ->dump() // which items qualified? ->map(fn ($item) => $item->price * $item->rule->percent / 100) ->dump() // per-item amounts ->sum(); ``` Because `->dump()` returns `$this`, the chain continues and the final value is unchanged. On a `LazyCollection`, `dump()` calls `all()`, which enumerates the whole source, so a dump inside a lazy pipeline over a large query loads everything at once. ## Query builders: the SQL, not the rows On a query builder the methods never run the query. They show what **would** run: 1. `->dump()` prints `toSql()` (for example `select * from "products" where "on_sale" = ? and "category_id" = ?`) and `getBindings()` (`[1, 7]`), then returns the builder so `->get()` can follow. 2. `->dd()` prints the same pair and stops. 3. `->dumpRawSql()` prints `toRawSql()`, the statement with each binding substituted and quoted by the connection's grammar: `... where "on_sale" = 1 and "category_id" = 7`. It returns the builder. 4. `->ddRawSql()` prints that raw SQL and stops. The raw form is what you paste into a database client to reproduce a wrong total. The placeholder form is closer to what the driver actually sends, and shows each binding's PHP type. `toRawSql()` itself returns a string, so it is the one to use in code that should keep running without output: ```php Log::debug('Discount query.', ['sql' => $query->toRawSql()]); ``` Treat raw SQL as a debugging aid only: substituting bindings for display is not how the query is executed, and the string should never be run back against the database with user input in it. ## The Eloquent detail worth knowing `Illuminate\Database\Eloquent\Builder` lists `dd`, `ddrawsql`, `dump` and `dumprawsql` in its **passthru** array. A passthru call runs `$this->toBase()->{$method}()`, and `toBase()` applies the model's global scopes first. Two consequences follow: - the dumped SQL includes global scope conditions, such as `"deleted_at" is null` from `SoftDeletes`, which is often exactly the clue a wrong total needs; - the method returns the **base query builder**, not the Eloquent builder, so `Product::where(...)->dump()->get()` gives plain `stdClass` rows rather than `Product` models. Dump in a separate statement, or use `dumpRawSql()` on a query variable, when you need models afterwards. ## More chainable dumps - `$request->dump('coupon', 'cart_id')` dumps only those input keys and returns the request. - `Http::dump()->post(...)` and `Http::dd()->post(...)` dump the outgoing request and its options just before sending; `dd()` ends the script without sending it. - `Benchmark::dd(fn () => $service->apply($cart), iterations: 10)` runs the closure, then dumps its average time in milliseconds and stops. ## Choosing the right call - Collection pipeline behaving oddly: `->dump()` between steps. - Query returns unexpected rows: `->dumpRawSql()` and run the SQL by hand. - Need the SQL in a log or test failure message: `toRawSql()`. - Need timing and every query of the request: a query log or Debugbar, not a dump.
- Why does Product::where('on_sale', true)->dump()->get() return stdClass objects instead of Product models?The Eloquent builder passes `dump` through to its base query builder via `toBase()->dump()`, and `dump()` returns that base builder. The following `get()` therefore runs on the plain query builder, which returns `stdClass` rows. Call `->dump()` in its own statement, or keep the Eloquent builder in a variable and dump a clone.
- The raw SQL from ddRawSql() shows "deleted_at" is null, which you never wrote. Where does it come from?Eloquent applies global scopes before forwarding the dump call, because passthru methods run on `toBase()`, which calls `applyScopes()`. A model using `SoftDeletes` adds `whereNull('deleted_at')` as a global scope, so the dump reflects the query that will really run.
saying these in an interview costs you the question
- Calling ->dump() on a query builder executes the query and shows the rows.
- ->dump() on a collection returns null, ending the chain.
- toRawSql() prints the query and stops the script.
- Eloquent dumps omit global scope conditions.
- The raw SQL string is what the driver actually sends to the database.