|
| 1 | +--- |
| 2 | +title: Stop using dd() |
| 3 | +--- |
| 4 | + |
| 5 | +Most PHP developers debug by printing. A `dd()` here, a `var_dump()` and `die` there, |
| 6 | +run it again, read the output, move the call somewhere else, run it again. Whole |
| 7 | +careers have been built on it, and it works often enough to feel like a method |
| 8 | +rather than a habit. |
| 9 | + |
| 10 | +It is worth asking why it became the default, because the reasons were real — and |
| 11 | +because most of them no longer hold. |
| 12 | + |
| 13 | +## Why it made sense |
| 14 | + |
| 15 | +For a long time, a step debugger for PHP was something you set up once, fought with, |
| 16 | +and gave up on. |
| 17 | + |
| 18 | +It was **hard to install**: a separate extension to get in place, and an editor to |
| 19 | +configure to match it. |
| 20 | + |
| 21 | +It was **slow**: loading it made every request noticeably worse, so it was something |
| 22 | +you enabled to hunt one bug and disabled immediately afterwards. |
| 23 | + |
| 24 | +It was **not there when you needed it**. Because it was slow, it stayed off. Because |
| 25 | +it stayed off, hitting a bug meant stopping, turning it on, restarting things, and |
| 26 | +trying to reproduce what you had just seen. By then, printing a variable and |
| 27 | +re-running had already answered the question. |
| 28 | + |
| 29 | +Against that, `dd()` was the rational choice. It needed nothing, cost nothing when |
| 30 | +absent, and was always available. |
| 31 | + |
| 32 | +## What changed |
| 33 | + |
| 34 | +All three of those reasons are gone. |
| 35 | + |
| 36 | +**The setup is one command.** The [installer](../getting-started/installation.mdx) |
| 37 | +works out what you are running and puts the right build in place. There are |
| 38 | +[prebuilt Docker images](../getting-started/docker.mdx) if you would rather not |
| 39 | +install anything at all. |
| 40 | + |
| 41 | +**It is fast.** With no editor connected the cost is close to nothing — the debugger |
| 42 | +tries once, finds no one listening, and gets out of the way. This is the whole point |
| 43 | +of the project. |
| 44 | + |
| 45 | +**It is always available.** Because it costs almost nothing, there is no reason to |
| 46 | +turn it off. Every request can be debugged, so the moment you want to look at |
| 47 | +something you already can — no restart, no configuration change, no reproducing the |
| 48 | +bug a second time. |
| 49 | + |
| 50 | +The debugger being off when you needed it was what made printing worth it. That is |
| 51 | +no longer true. |
| 52 | + |
| 53 | +## What printing cannot do |
| 54 | + |
| 55 | +Some of this is impossible with `dd()`. The rest is possible but so tedious that |
| 56 | +nobody does it. |
| 57 | + |
| 58 | +**See everything in scope, not just what you guessed.** `dd($order)` shows you |
| 59 | +`$order`. If the problem turns out to be `$discount`, that is another edit and |
| 60 | +another run. [The variables panel](./inspect-variables.md) shows all of it at once, |
| 61 | +including private and protected properties, without a single guess. |
| 62 | + |
| 63 | +**Look at the caller.** When a function chokes on what it was handed, the useful |
| 64 | +question is what the caller passed and where it came from. A debugger lets you |
| 65 | +[step up the call stack](./inspect-variables.md#stack-levels) and read the caller's |
| 66 | +variables at the moment it made the call. With printing you have to go and edit the |
| 67 | +caller too. |
| 68 | + |
| 69 | +**Carry on afterwards.** `dd()` kills the request — that is the second `d`. You see |
| 70 | +one moment and lose everything after it: the rest of the function, the response, |
| 71 | +the shutdown, the commit. A breakpoint pauses; you look, and then you |
| 72 | +[keep going](./step-debugging.md). |
| 73 | + |
| 74 | +**Watch a value change.** Printing gives you one snapshot per edit-and-run cycle. To |
| 75 | +see how a value evolves you add a dump per line, or a dump in a loop and then scroll |
| 76 | +through a thousand of them. Stepping shows you the change as it happens, and a |
| 77 | +[watch expression](./watches-and-edits.md) keeps the value in front of you at every |
| 78 | +stop. |
| 79 | + |
| 80 | +**Stop on the iteration that matters.** A dump inside a loop gives you every |
| 81 | +iteration. A [conditional breakpoint](./breakpoints.md#conditions) gives you the one |
| 82 | +where `$i > 1000 && $found === null` — the case you actually care about, with |
| 83 | +everything else running at full speed. |
| 84 | + |
| 85 | +**Catch a throw where it happens.** An |
| 86 | +[exception breakpoint](./breakpoints.md#exception-breakpoints) stops at the line |
| 87 | +that threw, before any `catch` runs, with the stack as it was. Printing gets you |
| 88 | +whatever the handler chose to log, from wherever it eventually ended up. |
| 89 | + |
| 90 | +**Change something and see what happens.** Force a retry path, hand an error handler |
| 91 | +a malformed response, push a value over a threshold — |
| 92 | +[edit the value in place](./watches-and-edits.md#editing-values) and carry on, |
| 93 | +rather than contriving the input or editing the code to fake it. |
| 94 | + |
| 95 | +**Debug what you cannot see.** A dump only helps if you can read the output. An API |
| 96 | +request, a queue worker, a webhook, a redirect, an AJAX call that a browser swallows |
| 97 | +— all of them are ordinary debugging sessions, and none of them show you a |
| 98 | +`var_dump()`. |
| 99 | + |
| 100 | +**Leave no trace.** Every `dd()` is an edit to your code, which means one can be |
| 101 | +committed, and eventually one is. Breakpoints live in your editor and cannot ship. |
| 102 | + |
| 103 | +## When it is still fine |
| 104 | + |
| 105 | +None of this makes printing wrong. |
| 106 | + |
| 107 | +If you want to know whether a line is reached, or what one variable holds at one |
| 108 | +point, and the answer ends the matter — print it. Starting a session to answer a |
| 109 | +question that a single `var_dump()` settles in five seconds is not a better |
| 110 | +workflow, it is a worse one. |
| 111 | + |
| 112 | +The line is roughly this: printing suits a question you already know the shape of. |
| 113 | +The moment you find yourself moving the call around, adding a second one, or running |
| 114 | +it a third time to see what changed, you have stopped answering a question and |
| 115 | +started searching. That is what the debugger is for, and it will be faster from the |
| 116 | +second run onwards. |
| 117 | + |
| 118 | +## Worth reconsidering |
| 119 | + |
| 120 | +If printing is your whole workflow, it is likely because of how debuggers used to |
| 121 | +be, not because of a comparison you have made recently. That is a reasonable way to |
| 122 | +have ended up here — the tools really were not good enough, and the habit really did |
| 123 | +work. |
| 124 | + |
| 125 | +The next time you reach for `dd()`, consider setting a breakpoint instead and seeing |
| 126 | +how far it gets you. Start your editor listening, put a breakpoint on the line you |
| 127 | +would have dumped from, and look around when it stops. There is nothing to install |
| 128 | +and nothing to turn on. |
| 129 | + |
| 130 | +If you have not tried it since the tooling got better, it is worth one afternoon to |
| 131 | +find out. |
0 commit comments