This article covers how to output strings in PHP, organised by where the output goes.
The short answer: echo writes to the page, embedding console.log() writes to the browser’s developer console, and error_log() writes to the server log without touching the page at all.
A debug echo that reaches production is a common enough accident. Knowing the three destinations is what stops it happening in the first place.
This article covers all three, how to pick between var_dump, print_r and var_export, and an escaping problem in the console approach that is easy to miss. Checked on PHP 8.5.
Sponsored
Three destinations
Choose by where you want the output to appear, not by what you are inspecting.
▼Which to use
| Method | Goes to | Use when |
|---|---|---|
echo |
The page (HTML) | The value is meant to be seen |
Embedded console.log() |
Browser devtools | You need the value without disturbing the layout |
error_log() |
The server log file | Production, async work, API responses |
Outputting a string with echo
echo writes what you give it straight into the page. It is the most basic form of output in PHP.
echo "The string to output";
It accepts several values separated by commas:
$name = "Rin";
echo "Hello, ", $name;
Always escape variables before echoing them. Echoing user input directly means any HTML or script in it is parsed as markup.
// unsafe: input is parsed as HTML
echo $comment;
// safe: special characters become entities
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
Since PHP 8.1 the default flags for htmlspecialchars() are ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, so single quotes are escaped even if you omit the arguments. Passing them explicitly keeps the behaviour identical if the code moves to an older environment.
Sponsored
Using echo inside HTML
Wrap it in PHP tags. The short echo tag <?= ?> saves writing echo at all.
<body>
<?php
echo "The string to output";
?>
</body>
The same thing with the short tag:
<body>
<?= htmlspecialchars($text, ENT_QUOTES, 'UTF-8') ?>
</body>
<?= is shorthand for <?php echo. Since PHP 5.4 it works regardless of the short_open_tag setting, which makes it the more readable choice inside templates.
Leave the closing ?> off the end of a PHP file. Any whitespace or newline after it becomes output, which is a frequent cause of “headers already sent” errors.
Writing to the browser console
To inspect a value without putting it on the page, output a <script> tag that passes it to console.log().
This suits arrays and objects. You can read the contents without disturbing the layout, which matters while you are still adjusting CSS.
<?php
$data = ["name" => "Rin", "tags" => ["php", "css"]];
echo '<script>';
echo 'console.log(' . json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE) . ')';
echo '</script>';
?>
Why json_encode needs those flags
Calling json_encode() with no flags here is unsafe. If the value contains the string </script>, the script tag closes early and everything after it is parsed as HTML.
With user-supplied input in the data, that is an XSS hole. JSON_HEX_TAG encodes < and > as < and >, which closes the route.
▼Flags to include
| Flag | Effect |
|---|---|
JSON_HEX_TAG |
Encodes < and > (the essential one) |
JSON_HEX_AMP |
Encodes & |
JSON_HEX_APOS |
Encodes ' |
JSON_HEX_QUOT |
Encodes " |
JSON_UNESCAPED_UNICODE |
Leaves non-Latin text readable |
JSON_UNESCAPED_UNICODE is about readability rather than safety. Without it, non-Latin characters appear as \u escapes — still valid, but much harder to scan.
This technique only works on pages that output HTML. Used inside a JSON API response or a CLI script, the injected <script> corrupts the output. Use error_log() there instead.
Sponsored
Logging without touching the page
error_log() writes to the server's error log. Nothing reaches the page or the response, so it is safe in production and inside APIs.
$data = ["name" => "Rin", "tags" => ["php", "css"]];
// a plain string
error_log("Reached this point");
// arrays need converting to a string first
error_log(print_r($data, true));
// JSON keeps it on one line, which is easier to grep
error_log(json_encode($data, JSON_UNESCAPED_UNICODE));
Do not forget the true in print_r($data, true). Without it, print_r prints to the page and passes its return value — literally 1 — to the log.
Where the log lands is set by error_log in php.ini. If it is unset, entries go to the web server's error log: Apache's error_log, or the PHP-FPM log on nginx.
tail -f /var/log/php-fpm/error.log
Choosing between var_dump, print_r and var_export
Use var_dump() when types matter, print_r() to read structure quickly, and var_export() when you want output you can paste back into code.
▼The three compared
| Function | Shows types | Can return a string | Best for |
|---|---|---|---|
var_dump() |
Yes, with lengths | No | Suspecting a type mismatch |
print_r() |
No | true as second argument |
Skimming the structure |
var_export() |
Yes, as PHP syntax | true as second argument |
Pasting straight into code |
When a value looks correct but a condition still fails, reach for var_dump(). print_r() cannot distinguish the string "0" from the integer 0, or null from an empty string.
var_dump($value);
// string(1) "0" ← the string zero
// int(0) ← the integer zero
// NULL ← null
To keep that off the page, combine it with the logging approach: error_log(print_r($data, true)).
Keeping debug output out of production
Standardise on error_log() and a forgotten debug line never reaches a visitor.
An echo or var_dump() left behind renders straight onto the page. var_dump() is the worse of the two, since it dumps whole structures and can expose internal paths and identifiers.
Writing debug output through error_log() from the start removes that risk entirely. When you do need it on screen, gate it on the environment:
if (getenv('APP_ENV') === 'local') {
echo '<pre>' . htmlspecialchars(print_r($data, true), ENT_QUOTES, 'UTF-8') . '</pre>';
}
If you are setting up a local PHP environment, building a Laravel and Blade environment with Docker covers a container-based setup you can debug inside.