Skip to content
beginner Phase 3 · Arrays and String Processing

String Interpolation and Heredoc

Build strings with embedded variables and multi-line literals.

30m
0 problems
Topic Progress 0%

Single vs Double Quotes: What the Parser Rewrites

PHP has two literal syntaxes for the same string type, and the difference is entirely about what the parser rewrites before the string exists:

$name = 'Ada';
echo "Hello, $name!\n";        // "Hello, Ada!" + newline — variable substituted, \n is a newline
echo 'Hello, $name!\n';        // "Hello, $name!\n" literally — 14 chars, backslash included

Double-quoted strings interpolate variables and process escapes. The recognized escape sequences are the familiar set — \n, \t, \r, \\, \", \$ — plus \u{1F600} for Unicode code points (PHP 7+), which is the clean way to embed non-ASCII characters in source without hunting for the literal glyph. A $ followed by a valid variable name is substituted; writing a literal $ before a word requires \$.

Single-quoted strings are almost entirely literal. Only two sequences are recognized: \\ (a backslash) and \' (a quote). Everything else arrives byte-for-byte, which is why single quotes are the default for anything with backslashes or dollar signs — Windows paths, regex patterns, shell fragments — and for plain text with no variables in it:

$re = '/^[a-z]+$/i';           // no double-escaping \s \d \w headaches
$path = 'C:\php\php.exe';      // one backslash in, one backslash out

The performance question ("are double quotes slower?") has a modern answer: negligibly, and not for the reason people think. Interpolation is resolved at compile time from the literal parts — the engine builds the concatenation plan once — so a double-quoted string with one variable is comparable to the explicit concatenation 'Hello, ' . $name . '!'. The old advice to prefer single quotes for speed is stale; the real reason to prefer single quotes is intent: a string with no interpolation in it cannot accidentally interpolate, and readers can see at a glance that nothing in it is dynamic.

Interpolating an undefined variable inside double quotes behaves like reading it anywhere else — a PHP 8 warning and an empty substitution — so the complex syntax pairs naturally with the null-coalescing operator when the value may be absent:

echo "Hello, {$user['name'] ?? 'guest'}";    // no warning, sensible default, one expression

The same brace form keeps a literal $ legible in a template without escaping it ("{$dollar}9.99"), and \u{...} gives an ASCII-only source a way to embed a bullet or an accented name without depending on the file's encoding surviving every git operation:

echo "Caf\u{00E9} \u{2014} open 8\u{2013}18\n";   // Café — open 8–18

Those two features — brace-wrapped defaults and \u{} escapes — cover nearly every place where the simple "$var" form runs out of road.

One placement rule covers array access and object properties in the simple syntax: "$user[name]" and "$user->name" work only for unquoted keys and plain property names. The moment a key needs quotes or the expression gets any more complex, you must use the brace form of the next chapter — $user['name'] inside a double-quoted string is a parse error without them, and it is one of the two or three interpolation facts every PHP interview eventually probes.

Complex Syntax: When a Dollar Sign Needs Braces

The simple "$user->name" form covers property reads and bare-key array access. The complex syntax {$ ... } covers everything else by making the boundary explicit:

echo "Order {$order['id']} for {$customer->firstName}";       // quoted key, property
echo "Total: {$cart->total($tax)}";                            // method call — allowed!
echo "Path: {$matrix[$i][$j]}";                                // nested access
echo "Sum: {$a} {$b}";                                        // braces prevent ambiguity: "$a {$b}" vs "$ab"

Anything valid inside {$ ... } is substituted — including method calls, arithmetic, and function calls — because the braces hand a real expression to the parser rather than asking it to guess where a variable name ends. The ambiguity case is the one to remember in reviews: "$user_id" is one variable named user_id; when you mean $user followed by the literal text _id, only braces ("{$user}_id") say so.

One syntax to retire on sight: ${name} interpolation is deprecated since PHP 8.2 (scheduled for removal in 9.0) in favor of {$name}. Legacy codebases still contain it, and a deprecation notice in the log is exactly how you find those files.

Escaping is the other half of complex syntax. Inside double quotes and heredocs, \$ produces a literal dollar, and doubled backslashes collapse as usual. But the safest escape from interpolation entirely is single quotes or nowdoc — a string containing user-shaped text ("$100 off", SQL with $_-prefixed placeholders, JSON templates) reads wrong when every $ needs a backslash. A useful middle path is printf, which keeps the template and the values separate:

printf('Order %s for %s: %.2f EUR', $id, $name, $total);

No $ parsing happens in the format string at all, positional placeholders survive translation workflows, and numeric formatting (%.2f, %05d) comes for free — the same reason it appeared in the output chapter.

The rule of thumb that resolves style debates: interpolate when the string is prose with holes; concatenate or printf when the pieces are computed. "Total: {$total} EUR" reads better than 'Total: ' . $total . ' EUR', while building a query from ten fragments with concatenation makes the seams visible — and visible seams are what let the prepared-statement habit (next chapter's neighbor) take hold.

If you do mix interpolation with concatenation and arithmetic, one PHP 8 precedence change belongs in your mental model. Before 8.0, . bound tighter than + and -, so 'Total: ' . $a + $b parsed as ('Total: ' . $a) + $b — string-converting $a, then adding — and producing a nonsense number where a total was expected. PHP 8 reordered the precedence so arithmetic happens first:

'Total: ' . $a + $b      // PHP 7: ('Total: ' . $a) + $b   — wrong
                          // PHP 8: 'Total: ' . ($a + $b)  — as written

Mixed expressions like this still deserve parentheses for the reader's sake, but the change explains a whole class of "the upgrade changed my output" bug reports — the same version-sensitivity theme as 0 == 'apples'.

The interpolation-vs-formatting choice in one comparison:

"Hello, {$name} — you have {$count} messages"     // prose with holes: interpolate
sprintf('%s ordered %d units at %.2f', $who, $n, $p)   // values with types: format
'prefix_' . $id                                   // computed fragments: concatenate

Each form makes a different promise about where the dynamic parts are, and choosing deliberately is most of what "string style" means in a reviewed codebase.

Heredoc and Nowdoc: Multi-line Literals Without the Backslash Forest

When a literal spans several lines — SQL, HTML fragments, CLI help text, JSON payloads — escaping quotes and newlines in one long double-quoted string becomes unreadable. Heredoc solves it with a start marker and a closing identifier:

$sql = <<<SQL
    SELECT id, email
      FROM users
     WHERE status = 'active'
       AND created_at > :since
SQL;

The rules that matter:

  • <<<IDENT opens; the identifier alone on a line closes it (with an optional ;).
  • Since PHP 7.3, the closing identifier may be indented, and that indentation is stripped from every line — so heredoc lives inside indented code without injecting leading spaces into the string.
  • Content between the markers behaves exactly like a double-quoted string: variables interpolate and escapes like \n are processed.

That last point is what separates heredoc from its twin. Nowdoc — with a quoted identifier — is the single-quoted equivalent: completely literal, no interpolation, no escapes:

$pattern = <<<'REGEX'
    /^[a-z][a-z0-9_]{2,15}$/i
REGEX;

The pairing is easy to remember: heredoc ≈ double quotes (dynamic), nowdoc ≈ single quotes (static). Both support complex syntax — {$user->name} works inside heredoc — and both produce ordinary strings, so every function from the string topic accepts them.

Where they pay off in practice: any multi-line text with quotes in it. A JSON API payload no longer needs \" at every corner; an email template keeps its paragraphs; a CLI usage screen prints aligned columns:

$usage = <<<TXT
    Usage: php import.php --file=<csv> [--dry-run]

      --file=PATH    CSV to import (required)
      --dry-run      Report what would happen; change nothing
TXT;

PHP 7.3 also removed the old requirement that a heredoc be assigned before use: it is a normal expression now, so a heredoc can sit inside a function call as an argument, which is where most modern code meets it:

$db->exec(<<<SQL
    UPDATE orders SET status = 'packed' WHERE id = :id
SQL);

$resp = $client->post('/notify', [
    'body' => json_encode(['text' => $body], JSON_PRETTY_PRINT),
]);

The indentation-stripping rule has one sharp edge worth internalizing: the closing marker's indentation is removed from every line, so a line inside the block that is indented less than the closing marker is a parse error, not a shorter string. Aligning the closing marker at the code's indentation level is what makes the feature safe to use inside classes and control structures.

And when the content must be truly inert, nowdoc is the vehicle of choice — regex patterns, serialized config, anything where a stray $ or backslash would silently change meaning:

$pattern = <<<'REGEX'
    /^[a-z][a-z0-9_]{2,15}$/i
REGEX;

$banner = <<<'ASCII'
    ** LEGACY NOTICE **
    This endpoint retires 2026-12-31.
ASCII;

The trap to state even though it belongs to a different topic: a heredoc-built SQL string is still string-built SQL. The syntax eliminated quote-escaping, not injection — placeholders plus prepared statements ($stmt->execute([':since' => $since])) remain mandatory, and heredoc is simply the pleasant way to write the statement template. Confusing "readable SQL" with "safe SQL" is a mistake interviewers actively look for, and the honest answer — heredoc for ergonomics, bindings for safety — is what they want to hear.

Quiz

1. What does `echo 'Cost: \$5\n';` print?

Question 1 options

2. Which of these is legal interpolation in a double-quoted string?

Question 2 options

3. Heredoc vs Nowdoc — the essential difference?

Question 3 options

4. You build SQL in a heredoc with a variable interpolated into the WHERE clause. What is the correct security statement?

Question 4 options

Flashcards

Question

What do double-quoted and single-quoted strings each process?

Answer

Double-quoted: variable interpolation plus escapes \n \t \r \\ \" \$ \u{...}. Single-quoted: literal text with only \\ and \' recognized. Prefer single quotes for text with backslashes or no variables; performance differences are negligible today.

Question

When is the {$...} complex syntax required inside a double-quoted string?

Answer

Quoted array keys ("{$row['id']}"), nested access, method calls ("{$cart->total()}"), and any spot where the variable name would otherwise run into following text ("{$user}_id"). Simple syntax covers only $var, $var[unquotedKey], $obj->property.

Question

Status of ${name} interpolation

Answer

Deprecated since PHP 8.2 and scheduled for removal in 9.0; use {$name} instead. Finding ${...} in a codebase is a reliable sign of pre-7.4-era string code worth scanning for other legacy patterns.

Question

Heredoc mechanics: closing marker, indentation, and interpolation

Answer

<<<ID opens, the identifier alone on a line closes it (optional ;). Since 7.3 the closing marker may be indented and that indentation is stripped from every content line. Content behaves like double quotes — variables and escapes apply; <<<'ID' (nowdoc) is the fully literal variant.

Question

What does heredoc NOT change about SQL safety?

Answer

Nothing. It only removes quote-escaping from the statement template; interpolating variables into SQL is still injection. Real safety is placeholders plus prepared statements ($stmt->execute([...])) — heredoc is ergonomics, bindings are security.

Revision Notes

Key Takeaways

  • 1. Double quotes interpolate and process escapes; single quotes are literal except \\ and \' — choose by intent, not by speed myths
  • 2. Complex syntax {$...} handles quoted keys, nesting, and method calls; simple syntax stops at $var[key], $obj->prop
  • 3. ${name} is deprecated since 8.2 — rewrite to {$name} when found
  • 4. Heredoc ≈ dynamic multi-line (double-quote semantics), nowdoc ≈ literal multi-line (single-quote semantics); 7.3+ allows indented closing markers with indentation stripping
  • 5. Heredoc fixes SQL ergonomics only — prepared statements with placeholders remain the actual injection defense

Interview Tips

  • • "Difference between ' and \" strings?" — interpolation and escapes, with the exact escape lists; add that the performance argument for single quotes is stale
  • • "How do you interpolate an array value with a quoted key?" — {$row['id']} complex syntax; mention ${name} deprecation to show version awareness
  • • "Heredoc or nowdoc for this SQL?" — heredoc for the template with placeholders, never for user values; the answer must pivot to prepared statements
  • • "A template needs a literal $ sign — what are the options?" — \\$ in double quotes/heredoc, single quotes or nowdoc, or printf with %s so no $ parsing occurs at all

Cheat Sheet

String Interpolation and Heredoc

Quote rules:

Syntax Interpolates Escapes processed
"..." yes — $var, {$expr} \n \t \\ \" \$ \u{...}
'...' no only \\ and \'
<<<ID ... ID (heredoc) yes — like double quotes like double quotes
<<<'ID' ... ID (nowdoc) no — like single quotes none

Interpolation forms:

"$user->name"          // property, simple syntax
"$row[total]"          // array with UNQUOTED key only
"{$row['total']}"      // complex syntax — quoted keys, method calls, nesting
"{$user}_id"           // braces when the next char would extend the variable name
"{$cart->total($tax)}" // method call inside the string is legal

${name} is deprecated since 8.2 — rewrite as {$name}.

Heredoc / Nowdoc (PHP 7.3+ indented closing marker, indentation stripped):

$sql = <<<SQL
    SELECT id FROM users WHERE status = :status
SQL;                      // interpolates — template, not safety

$re = <<<'REGEX'
    /^[a-z][a-z0-9_]{2,15}$/i
REGEX;                    // fully literal

Readability only: heredoc SQL still needs prepared statements; heredoc JSON needs no \" at the corners.

Continue Learning