SmartString makes template output XSS-safe by default: values HTML-encode themselves whenever they're printed. This page touches on everything you need to start working with the library, from installation to debugging.
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 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 it
produces a visible ' in your output rather than a silent security
hole.
$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 inverts the default behavior: output is encoded when you do nothing, and unencoded output has to be asked for 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 end the chain and return a plain PHP value.
$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.
print_r() can be used on any SmartString to show 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. value()returns your original value, in its original type. Anintgoes in and anintcomes back; same forfloat,bool, andnull.- Methods return new objects. Chaining never modifies the original SmartString.
- Chains never throw on bad data. Missing values pass through
transformations, and failed operations (an invalid date, math on a
non-numeric value) return null, so one
or()at the end covers anything that went wrong anywhere in the 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.