PHP Output
In PHP, “output” means anything your script sends back to whoever is running it — usually a web browser, but sometimes a command-line terminal. PHP gives you several tools for this: the everyday echo and print statements, formatted output with printf/sprintf, and debugging helpers like print_r and var_dump that show you exactly what a variable contains. Understanding the differences between them, and knowing when to use each, is one of the first real skills every PHP developer needs, because almost every script eventually has to show something to someone.
Overview: How Output Works in PHP
PHP is fundamentally a templating language wrapped around a scripting engine: everything outside <?php ... ?> tags is sent to the output stream verbatim, and everything inside the tags is executed by the Zend Engine. When your script calls echo, print, or a formatting function, PHP does not necessarily send bytes straight to the browser. Instead, the text is written to an internal output buffer managed by the SAPI (Server API) layer, the component that connects PHP to Apache, Nginx/PHP-FPM, or the command line. That buffer is flushed to the actual client either automatically or manually. This matters in practice: functions like header() or session_start() must run before any output is generated, because once even one byte has left PHP’s control, HTTP headers can no longer be changed.
PHP’s output tools fall into two groups. The first is direct output: echo and print, which are language constructs rather than ordinary functions, meaning they are built into the parser itself. That is why you can write echo without parentheses and pass it several comma-separated values at once. The second group is formatted or introspective output: functions such as printf(), sprintf(), print_r(), var_dump(), and var_export(), which give you control over how a value is displayed or let you inspect a variable’s exact type and structure, which is invaluable while debugging.
echo vs. print
echo and print both send text to the output buffer, and for everyday use they are interchangeable. The differences are subtle but worth knowing:
echohas no return value and accepts multiple comma-separated arguments, e.g.echo "a", "b", "c";.printonly ever accepts a single argument and always returns the integer1, which means it can be used inside a larger expression, e.g.$ok = print "done";.- Because
echonever needs to prepare a return value, it is very slightly faster thanprint— though the difference is immeasurable in real applications and should never influence your choice.
Output can also come from raw HTML mixed with PHP tags, or from the short echo tag <?= $value ?>, which is exactly equivalent to <?php echo $value; ?> and has been available since PHP 5.4 regardless of the short_open_tag setting.
Syntax
The general forms of PHP’s most common output tools:
echo expression1, expression2, ...;
print expression;
printf(string $format, mixed ...$values): int
sprintf(string $format, mixed ...$values): string
print_r(mixed $value, bool $return = false): string|bool
var_dump(mixed ...$values): void
var_export(mixed $value, bool $return = false): string|null
| Construct | Returns | Purpose |
|---|---|---|
echo |
none | Send one or more strings to output |
print |
int (always 1) |
Send a single string; usable as an expression |
printf() |
int |
Send formatted output (padding, decimals, etc.) |
sprintf() |
string |
Build a formatted string without printing it |
print_r() |
string|bool |
Human-readable dump of arrays and objects |
var_dump() |
none | Type-and-value dump, including nested structures |
var_export() |
string|null |
Dump as valid, re-usable PHP code |
Examples
Example 1: echo and print basics
<?php
$name = "Ava";
$age = 29;
echo "Hello, " . $name . "! You are " . $age . " years old.\n";
echo "Hello, ", $name, "! You are ", $age, " years old.\n";
print "Hello again, $name!\n";
$result = print "This line always returns 1.\n";
echo "print() returned: $result\n";
Output:
Hello, Ava! You are 29 years old.
Hello, Ava! You are 29 years old.
Hello again, Ava!
This line always returns 1.
print() returned: 1
The first two lines produce identical output using different syntax: string concatenation with ., and comma-separated arguments to echo (which PHP writes out one after another without adding anything between them). The print statement only accepts one expression, so string interpolation is used to embed the variable directly inside the string. Finally, $result shows that print always evaluates to 1, regardless of what it printed.
Example 2: print_r, var_dump, and formatted output
<?php
$user = [
'name' => 'Marcus',
'roles' => ['admin', 'editor'],
];
echo "Using print_r:\n";
print_r($user);
echo "\nUsing var_dump:\n";
var_dump($user['roles']);
$price = 19.5;
printf("Price: $%.2f\n", $price);
$formatted = sprintf("%-10s|%5d", "SKU-42", 3);
echo $formatted . "\n";
Output:
Using print_r:
Array
(
[name] => Marcus
[roles] => Array
(
[0] => admin
[1] => editor
)
)
Using var_dump:
array(2) {
[0]=>
string(5) "admin"
[1]=>
string(6) "editor"
}
Price: $19.50
SKU-42 | 3
print_r() renders arrays and objects in a readable, indented layout, great for a quick look at structure, but it does not show data types. var_dump() is more precise: it prints each value’s type and length (string(5) "admin" tells you it is a 5-character string), which is exactly what you need when a bug turns out to be caused by "3" behaving differently from 3. printf() and sprintf() use the same format-specifier syntax as C’s printf: %.2f rounds and pads a float to two decimals, %-10s left-aligns a string in a 10-character field, and %5d right-aligns an integer in a 5-character field.
Example 3: heredoc and nowdoc
<?php
$product = "Wireless Mouse";
$price = 24.99;
$tags = ['electronics', 'accessories'];
$description = <<<EOT
Product: {$product}
Price: \${$price}
Tags: {$tags[0]}, {$tags[1]}
EOT;
echo $description . "\n\n";
$template = <<<'EOT'
Raw text with $variables left untouched.
No {$interpolation} happens here.
EOT;
echo $template . "\n";
Output:
Product: Wireless Mouse
Price: $24.99
Tags: electronics, accessories
Raw text with $variables left untouched.
No {$interpolation} happens here.
Heredoc (<<<EOT ... EOT;) behaves like a double-quoted string that can span multiple lines: variables and {$expr} expressions are interpolated, and a literal dollar sign must be escaped with \$. Nowdoc (<<<'EOT' ... EOT;, note the quotes around the opening identifier) behaves like a single-quoted string: nothing is interpolated, which makes it ideal for output templates, code samples, or any text that happens to contain $ or curly braces you want to keep literal. The closing identifier must start at the beginning of a line (optionally indented to match the opening <<< since PHP 7.3) and must not have anything after it except a semicolon or comma.
Example 4: capturing output with buffering
<?php
function renderGreeting(string $name): string
{
ob_start();
echo "Hello, {$name}!\n";
echo "Welcome back.\n";
return ob_get_clean();
}
$html = renderGreeting("Priya");
echo strtoupper($html);
Output:
HELLO, PRIYA!
WELCOME BACK.
ob_start() tells PHP to redirect all subsequent output into an internal buffer instead of sending it to the client immediately. ob_get_clean() returns everything that was buffered as a string and simultaneously stops buffering and discards the buffer, so nothing is sent twice. This pattern is how many template engines and PDF/email libraries capture echo-based output so it can be transformed, cached, or embedded elsewhere before it is actually sent.
How It Works Step by Step
When PHP executes a statement that produces output, several things happen in order:
- The value is converted to a string using PHP’s normal string-conversion rules: numbers are formatted as decimal text,
truebecomes"1",falseandnullbecome an empty string, and arrays trigger anArraynotice unless you useprint_r()orvar_dump()to inspect them properly. - The resulting bytes are appended to the current output buffer. If your script called
ob_start(), this is a user-level buffer; otherwise it is the SAPI’s own buffer. - The buffer is flushed to the client automatically when it fills up, when the script ends, or manually via
flush()orob_flush(). - Once any byte has actually reached the client (or the buffer has been flushed), functions that modify HTTP headers, such as
header()orsetcookie(), will fail with a “headers already sent” warning.
This is also why output buffering is so useful during development: wrapping a whole request in ob_start() lets you set headers or redirect late in the request lifecycle, even after some output-producing code has already run.
Common Mistakes
Mistake 1: Passing print() multiple arguments
Unlike echo, print is a construct that takes exactly one expression. Trying to pass it several comma-separated values is a parse error:
print "Score: ", 95, "\n";
PHP cannot parse the comma after "Score: " because print is not a variadic construct. Use echo for multiple arguments, or concatenate them into one expression for print:
echo "Score: ", 95, "\n";
Mistake 2: Forgetting print_r()’s second argument
By default, print_r() outputs directly and returns true. Assigning its return value without passing true as the second argument captures the wrong thing:
$data = ['status' => 'ok', 'count' => 3];
$dump = print_r($data);
echo $dump;
This prints the array dump immediately (a side effect of the first call), and then echo $dump prints the literal string "1", because $dump only holds the boolean true that print_r() returned. Pass true as the second argument to capture the formatted string instead of printing it:
$data = ['status' => 'ok', 'count' => 3];
$dump = print_r($data, true);
echo $dump;
Mistake 3: Echoing untrusted input without escaping
Sending user-supplied data straight to echo inside an HTML page is a classic cross-site scripting (XSS) hole:
echo "Welcome, " . $_GET['username'];
If $_GET['username'] contains <script>...</script>, it runs in the visitor’s browser exactly as written. Always pass untrusted values through htmlspecialchars() (or a templating engine that escapes by default) before echoing them into HTML:
echo "Welcome, " . htmlspecialchars($_GET['username'], ENT_QUOTES, 'UTF-8');
Best Practices
- Default to
echofor everyday output; reach forprintonly when you specifically need its return value inside an expression. - Use
printf()/sprintf()whenever output needs consistent formatting, such as currency, fixed decimal places, or column alignment. - Use
var_dump()while debugging to see both type and value, but strip or gate debug output behind an environment check before deploying to production. - Always run untrusted or user-supplied data through
htmlspecialchars()before echoing it into an HTML context. - Prefer heredoc over long chains of concatenation when building multi-line strings with several interpolated variables.
- Prefer nowdoc when the text should NOT be interpolated, such as code samples or literal templates containing
$signs. - Reach for output buffering (
ob_start()/ob_get_clean()) whenever you need to capture, transform, or delay output rather than send it immediately. - Never call output functions before
header()orsession_start()if you still need to modify HTTP headers later in the request.
Practice Exercises
- Write a script with an associative array describing a product (
name,price,stock). Useprintf()to print one line showing the name left-aligned in 15 characters, the price with exactly two decimal places, and the stock as a right-aligned 4-digit integer. - Use a heredoc to build a multi-line plain-text email that includes a customer’s name and an order total pulled from variables, then
echothe result. - Write a function
debugValue(mixed $value): stringthat usesprint_r()with its return-mode argument to produce a labeled debug string like"DEBUG: Array(...)"without printing anything directly inside the function.
Summary
echoandprintare language constructs, not functions;echoaccepts multiple arguments and returns nothing, whileprintaccepts one argument and always returns1.print_r()gives a readable structural dump;var_dump()additionally shows each value’s type and length, which matters when debugging type-related bugs.printf()andsprintf()provide C-style formatting for decimals, padding, and alignment.- Heredoc interpolates variables like a double-quoted string; nowdoc does not interpolate anything, like a single-quoted string.
- Output is buffered by the SAPI layer, which is why headers must be set before any output is generated, and why
ob_start()/ob_get_clean()can capture output as a string. - Always escape untrusted data with
htmlspecialchars()before echoing it into HTML to prevent XSS.
