Browse by section

Web Design & Dev 日本語

PHP String Output: echo, console.log and error_log Compared

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.