Encoding and HTML
Auto-encoding is SmartString’s main job, and this page covers all of it: how the encoding works, the methods for URL and JSON contexts, and the named methods that let markup through. Markup reaches your page only through those named methods; everything else comes out encoded.
How Auto-Encoding Works
Section titled “How Auto-Encoding Works”Whenever a SmartString is used in a string context, PHP calls the object’s
__toString() method, which runs htmlspecialchars() on the raw value. All
of these produce encoded output:
$str = SmartString::new("It's <b>easy</b> & fun!");
echo $str; // It's <b>easy</b> & fun!print $str; // same$html = "Value: $str"; // interpolation encodes too$cast = (string) $str; // explicit cast encodes tooEvery one of those lines replaces writing this by hand:
echo htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_DISALLOWED | ENT_HTML5, 'UTF-8');Those flags cover the five characters that enable HTML injection (<, >,
&, ', "), and substitute malformed UTF-8 and forbidden code points
with � instead of letting them hide in page source.
Your original data stays intact inside the object, and nothing is encoded until the moment a value is printed. Wrapping a value just stores it, so it costs next to nothing; encoding work happens only for values that actually reach the page.
Encoding Is Output, Not Storage
Section titled “Encoding Is Output, Not Storage”You never need to store encoded data. Encoding is an output step: store the raw value, and it gets encoded automatically on its way into the page:
// WRONG - database now contains "Jean O'Brien"$name = htmlspecialchars($_POST['name']);DB::insert('users', ['name' => $name]);
// RIGHT - store raw, encode on outputDB::insert('users', ['name' => $_POST['name']]);Storing raw data preserves integrity: you can encode it differently for different contexts (HTML, URL, JSON) without re-decoding stored data.
The Encoding Methods
Section titled “The Encoding Methods”These return the encoded value as a plain string, so they end the chain.
Missing values (null or "") return "", so echoing an empty field prints
nothing. The one exception is jsonEncode(), which encodes null as null and an
empty string as a quoted "", because its output must always be a valid
JavaScript expression.
HTML Encoding - htmlEncode()
Section titled “HTML Encoding - htmlEncode()”Same output as echo, as an explicit call. It returns a plain encoded string
rather than an object, and makes the encoding visible in the code:
$title = SmartString::new('<10% OFF "SALE"');
echo $title->htmlEncode(); // <10% OFF "SALE"echo $title; // same output - echo also encodesURL Encoding - urlEncode()
Section titled “URL Encoding - urlEncode()”Returns the value URL-encoded for query strings, where SmartString’s automatic HTML encoding is not URL-safe:
$title = SmartString::new('<10% OFF "SALE"');
echo "<a href='search.php?title={$title->urlEncode()}'>Search</a>";// <a href='search.php?title=%3C10%25+OFF+%22SALE%22'>Search</a>For a file or path that might have spaces in the name, use
map('rawurlencode'). The + from urlEncode() looks nicer in URLs, but
only query strings read it as a space - files and paths need %20:
$file = SmartString::new('Annual Report 2026.pdf');
echo "<a href='/uploads/{$file->map('rawurlencode')}'>Download</a>";// <a href='/uploads/Annual%20Report%202026.pdf'>Download</a>JSON Encoding - jsonEncode()
Section titled “JSON Encoding - jsonEncode()”Returns the value as a JSON string safe to embed in a <script> block. The
characters that could break out of a script or a JS string (" ' < >
&) are escaped as \uXXXX:
$title = SmartString::new("It's <b>easy</b> & fun!");
echo "<script>let title = {$title->jsonEncode()};</script>";// <script>let title = "It\u0027s \u003Cb\u003Eeasy\u003C/b\u003E \u0026 fun!";</script>Types encode as themselves: null becomes null, 123 becomes 123, so the
result is always a valid JavaScript expression. Two hardening details:
- Malformed UTF-8 is substituted with � instead of throwing.
- Invisible Unicode (zero-width characters, bidi controls, variation
selectors) is re-escaped as visible
\uXXXXescapes, so nothing can hide in page source.
Newlines to <br> - nl2br()
Section titled “Newlines to <br> - nl2br()”Encodes the value and then converts newlines to <br> tags, in that order,
so the only tags in the result are the ones it added. An enhancement of
PHP’s native nl2br() which doesn’t encode.
$address = SmartString::new("Bob & Sons\nSuite 5");
echo $address->nl2br();// Bob & Sons<br>// Suite 5The result is a plain string, already encoded and ready to print, even with hostile input:
$comment = SmartString::new("Nice!\n<script>alert('xss')</script>");
echo $comment->nl2br();// Nice!<br>// <script>alert('xss')</script>Trusted HTML - rawHtml()
Section titled “Trusted HTML - rawHtml()”Some fields legitimately contain HTML, most commonly WYSIWYG editor content.
For those, rawHtml() returns the original value unencoded. It behaves the
same as value(), the raw-value escape hatch from
Getting Started; the
different name signals intent to other developers:
“I know this is unencoded, and that is deliberate.”
echo <<<__HTML__ <h1>{$article->title}</h1> {$article->wysiwygContent->rawHtml()}__HTML__;The title auto-encodes; the WYSIWYG content outputs as-is. Never pass
unreviewed user input through rawHtml(); it bypasses encoding entirely.
Reserve it for content produced by trusted editors, and let everything else
take the encoded default.
Adding Markup Around Values - appendHtml() and wrapHtml()
Section titled “Adding Markup Around Values - appendHtml() and wrapHtml()”A common need for raw output is appending one tag next to an encoded value:
a <br> after each address line, an <h2> around a heading, etc.
These two methods cover that case without giving up encoding. The value
stays HTML-encoded; your markup is appended or wrapped as-is; and when
the value is missing (null or ""), the whole result is "" so no stray
markup appears:
// appendHtml($html): encoded value + your markup - addressLine2 is blank, so its line vanishesecho $member->addressLine1->appendHtml("<br>\n"); // 12 High St<br>echo $member->addressLine2->appendHtml("<br>\n"); // "" (blank: no stray <br>)echo $member->city->appendHtml("<br>\n"); // Vancouver<br>echo $member->country; // Canada
// wrapHtml($before, $after): markup + encoded value + markup - wrapper vanishes when missingecho $page->subheading->wrapHtml('<h2 class="lead">', '</h2>'); // <h2 class="lead">Our Story</h2>echo $member->email->wrapHtml('<a href="mailto:', '">Email me</a>'); // <a href="mailto:jean@example.com">Email me</a>echo $page->tagline->wrapHtml('<h2>', '</h2>'); // "" (tagline is empty: no empty <h2>)Zero is a real value, not a missing one, so a legitimate 0 still gets its
markup.
These methods trust their markup arguments and output them as-is: pass only literals you wrote, never user input. That is the same obligation rawHtml() carries, narrowed to one argument.
The return value is a plain string, ending the chain - no accidental double-encoding with additional chained methods.
When you’re adding plain text (a label, a comma) rather than markup, use the regular append(), prepend(), and wrap() methods instead; they encode added text like any other output and the chain stays open.
Both sides of wrapHtml() are required, and the value is inserted exactly
once; that is why the email example above uses fixed link text. When the
value needs to appear more than once, or the block spans several fields,
check isNotEmpty() and write the HTML in plain PHP:
// value used twice: the address is both the href and the link textif ($member->email->isNotEmpty()) { echo "<a href='mailto:$member->email'>$member->email</a>";}
// multiple fields in one block: photo with the name as alt textif ($member->photo->isNotEmpty()) { echo "<img src='/photos/$member->photo' alt='$member->name'>";}