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.
Contents:
- How Auto-Encoding Works
- Encoding Is Output, Not Storage
- The Encoding Methods
- Newlines to
<br>-nl2br() - Trusted HTML -
rawHtml() - Adding Markup Around Values -
appendHtml()andwrapHtml()
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.
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 output
DB::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.
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.
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 encodesReturns 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>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.
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>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.
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 vanishes
echo $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 missing
echo $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 text
if ($member->email->isNotEmpty()) {
echo "<a href='mailto:$member->email'>$member->email</a>";
}
// multiple fields in one block: photo with the name as alt text
if ($member->photo->isNotEmpty()) {
echo "<img src='/photos/$member->photo' alt='$member->name'>";
}← Documentation Index | ← Prev: Getting Started | Next: Text and Formatting →