One escaping function is not enough: "safe" depends on where the value lands. A string inert in HTML text can break out of an unquoted attribute, and one fine in an attribute can run as code in a <script>, so match the encoder to the context.
| Context | Encode with | Why |
|---|---|---|
| HTML text and quoted attributes | htmlspecialchars($v, ENT_QUOTES) | turns < > & " ' into entities |
| Unquoted attribute | quote it first, then htmlspecialchars | a space or > ends the attribute |
| Inside <script> (as data) | json_encode($v, JSON_HEX_TAG|JSON_HEX_APOS) | valid JS literal, no break-out |
| URL query value | rawurlencode($v) then escape the URL for HTML | keeps & and / from splitting the URL |
Subsection 4.6.11 runs all four on a live payload and prints the results. The key point: json_encode with the JSON_HEX_* flags produces a JS literal that cannot terminate the surrounding <script>, which a bare htmlspecialchars cannot do. Never assume one htmlspecialchars pass covers a value that also appears in a script or URL; Blade (Laravel) automates only the HTML case.