Skip to content

Working with Results

Every query that reads rows returns result objects: collections you can loop like arrays, and values that HTML-encode themselves on output. (insert(), update(), and delete() return plain ints.) This page covers the result hierarchy, output encoding, and the methods available at each level.

Query → Result (SmartArrayHtml) → Rows (SmartArrayHtml) → Values (SmartString)
$users = DB::select('users'); // result - collection of rows
foreach ($users as $user) { // row - one record
echo $user->name; // value - HTML-encodes itself
}

Values HTML-encode themselves in string context, so echo, print, and string interpolation are XSS-safe with no extra effort. For other contexts, ask for the encoding you need.

ExpressionResult
$user->nameHTML-encoded (string context)
$user->name->value()Original raw value and type
$user->name->rawHtml()Unencoded, for HTML you trust (alias of value())
$user->name->urlEncode()URL-encoded
$user->name->jsonEncode()JSON-encoded
// HTML context - encodes automatically
echo "<p>$user->name</p>";
// URL parameter
echo "<a href='/profile?name={$user->name->urlEncode()}'>Profile</a>";
// JavaScript
echo "<script>let name = {$user->name->jsonEncode()};</script>";
// Logic - compare the raw value, not the encoded string
if ($user->isAdmin->value()) {
echo "Admin";
}

rawHtml() is the one output path that skips encoding. Call it only on HTML you control and trust; Security Gotchas covers why it’s the single name for unencoded output.

Access columns with object notation. For a column that may be empty, chain or() for a fallback:

echo $user->name;
echo $user->nickname->or('Anonymous'); // fallback when null or ''

value() returns one field’s original value and type; toArray() converts a row or a whole result to plain PHP arrays:

$user = DB::selectOne('users', ['id' => 1]);
$name = $user->name->value(); // "O'Brien & Sons" - exactly as stored
$data = $user->toArray(); // ['id' => 1, 'name' => "O'Brien & Sons", ...]
$rows = DB::select('users')->toArray(); // array of plain row arrays

Value methods return a new SmartString, so transformations chain:

// Strip HTML tags, then shorten to 100 characters with an ellipsis
echo $article->body->textOnly()->maxChars(100);
// Format a number and prepend a currency symbol (a blank price stays blank, no stray $)
echo $product->price->numberFormat(2)->prepend('$'); // $1,234.56
// Format a date; supports years 1000-9999, anything else
// (null, invalid, zero dates like 0000-00-00) falls through to the or()
echo $user->lastLogin->dateFormat('M j, Y')->or('Never');

The result objects describe themselves when inspected; print_r() shows the data, and ->debug() adds the executed SQL and MySQL metadata:

print_r($users); // rows and values
print_r($user); // one row's columns and values
print_r($user->name); // one value's raw data
$users->debug(); // the executed SQL, rows, and MySQL metadata

CMS Builder users: showme() does the same thing as print_r(), wrapped in <xmp> tags for readable browser output.

Results carry their MySQL metadata, most useful with DB::query().

MethodReturns
$result->mysqli('insert_id')Auto-increment ID from an INSERT
$result->mysqli('affected_rows')Rows changed by INSERT/UPDATE/DELETE
$result->mysqli('query')The executed SQL
$result->mysqli()All metadata as an array
$result = DB::query("INSERT INTO ::users SET name = ?", 'Alice');
$newId = $result->mysqli('insert_id');

That’s for inserts written as raw SQL; DB::insert() returns the new ID directly, no metadata call needed.

The most used methods on the collection returned by DB::select() and DB::query(). This isn’t the full list; see SmartArray for everything.

MethodDescription
count($result)Number of rows ($result->count() works too)
$result->first()First row (SmartNull when the result is empty; chaining still works)
$result->toArray()Plain array of raw row arrays
$result->column('col')One column as a new collection
$result->sortBy('col')Sort rows by column
$result->filter(fn)Keep rows where the callback returns true
$result->where('col', $val)Keep rows where the column matches a value
$result->map(fn)Transform each row
$result->indexBy('col')Lookup array keyed by column
$result->groupBy('col')Groups of rows keyed by column value
use Itools\SmartString\SmartString;
$users = DB::select('users', ['status' => 'active']);
echo count($users) . " active users";
// One column
$names = $users->column('name'); // collection: ['Alice', 'Bob', 'Charlie', ...]
// Lookup by primary key - the ->{'...'} syntax reads keys plain property syntax can't, like numbers
$byId = $users->indexBy('id');
echo $byId->{'42'}->name;
// Group rows by a column value
$byCity = $users->groupBy('city');
foreach ($byCity as $city => $cityUsers) {
$city = SmartString::new($city); // foreach keys come back plain; this makes them encode like fields
echo "<h2>$city (" . count($cityUsers) . ")</h2>";
}

Each row in a collection, and the return value of DB::selectOne().

MethodDescription
$row->columnNameColumn value as SmartString
$row->{'users.name'}Column whose key plain syntax can’t type: Smart Join keys, numeric indexes
$row->keys()Column names
$row->values()Column values
$row->toArray()Raw associative array
$row->isEmpty()True when no row was found

Each column value is a SmartString. These are the most used methods.

Text

MethodDescription
->textOnly()Remove HTML tags, decode entities, trim
->maxChars(100)Shorten to N characters with ellipsis
->maxWords(20)Shorten to N words with ellipsis
->nl2br()HTML-encode, then newlines to <br> (returns a plain string)
->trim()Trim whitespace

Formatting

MethodDescription
->dateFormat('M j, Y')Format a date: “Sep 10, 2026”
->numberFormat(2)Format a number: “1,234.56”
->int(), ->float()Convert to a plain PHP type

Conditional fallbacks

MethodApplies when
->or('N/A')Value is null or '' (zero stays)
->ifNull('N/A')Value is null
->ifZero('Free')Value is numeric zero
->append(' items')Appends when value is present (including zero)
->prepend('$')Prepends when value is present (including zero)

These objects come from ZenDB’s companion libraries, and the complete method lists live in their own docs: