Sanitizing makes input fit a rule on arrival (filter_var(), Subsection 4.14.6). Escaping encodes a value for the context it is printed into, at output time. "Cleaned" input still breaks a page inside an attribute or a script, so store what the user typed and escape every value where you print it.
| Output context | Escape with |
|---|---|
| HTML text, quoted attribute | htmlspecialchars() with ENT_QUOTES |
| URL query value | rawurlencode(), then HTML-escape the URL |
| Inline JavaScript data | json_encode() with the JSON_HEX_* flags |
| SQL, shell | Prepared statements (Databases with PDO), escapeshellarg() (Command Injection) |
<?php
function e(?string $v): string
{
return htmlspecialchars($v ?? '', ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}
$comment = "<script>alert('x')</script>\n5 > 3 & \"ok\"";
$name = '" onmouseover="alert(1)';
$q = 'rock & roll/2026';
echo strip_tags('<a href="javascript:alert(1)">Click</a> <b>x</b>', '<a>'), "\n";
echo urlencode($q), ' ', rawurlencode($q), ' ', json_encode('</script>', JSON_HEX_TAG), "\n";
?>
<p><?= nl2br(e($comment)) ?></p>
<input value="<?= e($name) ?>">
<a href="/search?q=<?= e(urlencode($q)) ?>">More</a>Output
<a href="javascript:alert(1)">Click</a> x rock+%26+roll%2F2026 rock%20%26%20roll%2F2026 "\u003C\/script\u003E" <p><script>alert('x')</script><br /> 5 > 3 & "ok"</p> <input value="" onmouseover="alert(1)"> <a href="/search?q=rock+%26+roll%2F2026">More</a>
Since PHP 8.1 the default flags include ENT_QUOTES | ENT_SUBSTITUTE; the e() helper adds HTML5 entities and makes the choice explicit. Call nl2br() after escaping. strip_tags() is no defense: it passed the javascript: link. Blade escapes {{ }} for you (Laravel), Subsections 4.17.5-4.17.7 cover cross-site scripting, and PHP 8.5's URI extension parses whole URLs (Subsection 4.18.5).