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:

  • echo has no return value and accepts multiple comma-separated arguments, e.g. echo "a", "b", "c";.
  • print only ever accepts a single argument and always returns the integer 1, which means it can be used inside a larger expression, e.g. $ok = print "done";.
  • Because echo never needs to prepare a return value, it is very slightly faster than print — 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, true becomes "1", false and null become an empty string, and arrays trigger an Array notice unless you use print_r() or var_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() or ob_flush().
  • Once any byte has actually reached the client (or the buffer has been flushed), functions that modify HTTP headers, such as header() or setcookie(), 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 echo for everyday output; reach for print only 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() or session_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). Use printf() 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 echo the result.
  • Write a function debugValue(mixed $value): string that uses print_r() with its return-mode argument to produce a labeled debug string like "DEBUG: Array(...)" without printing anything directly inside the function.

Summary

  • echo and print are language constructs, not functions; echo accepts multiple arguments and returns nothing, while print accepts one argument and always returns 1.
  • 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() and sprintf() 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.