Most old code keeps working after an upgrade:
- If it breaks, it tells you. Old names phase out over multiple releases - IDE strikethrough, then a quietly logged notice with your file and line (CMS Builder shows these in the Developer Log), then a clear error - always naming the replacement.
- Everything worth checking is listed here. Silent behavior changes, deprecations, and optional renames, per version, each with a search that finds affected code.
Upgrading SmartArray also upgrades SmartString, and SmartArrayHtml returns its values as SmartString objects, so check its upgrade notes too.
Full lists of what changed per release: CHANGELOG.md.
Follow this section when upgrading from SmartArray before v3.0.0
(or CMS Builder before 3.85). Requires PHP 8.1+ and SmartString 3.0+
(earlier releases accepted any SmartString version); Composer updates it
automatically unless your composer.json pins itools/smartstring lower.
Creating an array with a boolean that contradicts the class used to be silently ignored -
SmartArray::new($data, true)looked like it asked for HTML-safe output but returned raw, unencoded values. It now throws at the call site:$rows = SmartArray::new($records, true); // throws: use SmartArrayHtml::new($data) instead $rows = SmartArrayHtml::new($records); // correct - values HTML-encode on outputRedundant booleans (
falseon SmartArray,trueon SmartArrayHtml) raise a deprecation notice and keep working.Fix:
- Search for creation calls passing a boolean and use the class that matches the intent instead
Regex:
(new SmartArray\w*|SmartArray\w*::new)\([^)]*(true|false)\)- also searchuseSmartStrings
Undocumented methods with no found uses were removed:
usingSmartStrings()- check the class instead; it is the mode:$arr instanceof SmartArrayHtmlsetLoadHandler()- pass the handler as a constructor property:new SmartArray($data, ['loadHandler' => $handler]). Setting it after construction never worked on record sets - rows are built during construction and never saw a handler set later.newSmartNull()- now protected; it built the internal missing-value placeholders and had no use outside the librarySearch:
usingSmartStrings|setLoadHandler|newSmartNull
sortBy(string $field, int $flags = SORT_REGULAR)- the second parameter was named$typebut always held PHP sort flags. It now matchessort()and PHP's own sort functions. Only named-argument calls are affected:$rows->sortBy('name', type: SORT_NATURAL); // before $rows->sortBy('name', flags: SORT_NATURAL); // afterRegex:
->sortBy\([^)]*type:
These now match plain PHP arrays: a column whose value is NULL reads as missing, so
isset()answers false,empty()answers true, and??returns its fallback. Previously they answered "does the column exist", which meant??fallbacks never fired on NULL columns in HTML mode - the wrapped null echoed as"":$row = SmartArrayHtml::new(['nickname' => null]); echo $row->nickname ?? 'none'; // before: "" - after: none isset($row->nickname); // before: true - after: false empty($row->nickname); // before: false - after: trueBracket syntax (
isset($row['field'])) changes the same way. Direct access is unchanged:$row->nicknamestill returns the stored null (wrapped in HTML mode) with no warning.Fix:
- Check templates using
??on nullable columns - they print the fallback where they used to print nothing. That's usually the intent; if the fallback carries user data, use->or()instead, which HTML-encodes. A??fallback skips encoding because PHP substitutes it before the library runs.- When migrating deprecated
get($key, $default)calls to$row->key ?? $default:get()returns a stored NULL instead of the default, the??form returns the default. Same results everywhere except NULL columns.- To ask "does the key exist, even if NULL", use
$row->keys()->contains('field').Regex:
->\w+ \?\?- also searchisset(andempty(on row fields
Most calls behave the same: numbers still match numeric strings, so
where('id', 5)matches'5'andwhere('price', 1)matches'1.00'. Three edge cases now match fewer rows:$rows->where('code', '0e123'); // before: also matched '0e999' (PHP read both strings as numbers) // after: strings must match exactly ('01' vs '1' changed the same way) $rows->where('field', null); // before: matched null, '', 0, and false // after: matches only null, like SQL IS NULL $rows->where('active', true); // before: matched anything truthy, even 'abc' // after: true means 1, so it matches 1 and '1'Fix:
- For empty/non-empty checks, use
where($field)/whereNot($field)- When you mean a number, pass a number:
where('price', (float)$_GET['price'])Regex:
->(where|whereNot|contains)\([^)]*(null|true|false)\s*\)
Keying rows by a float field now throws instead of using PHP's float-to-int key truncation (
19.99and19.50both keyed as19, losing a row, plus a PHP deprecation notice):$products->indexBy('price'); // InvalidArgumentException: indexBy(): 'price' has float values, // convert them to strings firstConvert the field to a string first:
CAST(price AS CHAR)in SQL, or format it in PHP before keying.
where(),whereNot(),whereInList(),sortBy(),indexBy(),groupBy(),column(), andcolumnAt()now follow one rule: they work on the rows (elements that are arrays) and ignore other elements. A non-empty array with no rows throwsInvalidArgumentException, same as before; an empty array returns an empty result.In v2.x each method handled a scalar next to rows differently: the
where()family already skipped scalars (unchanged),sortBy()sorted them in with the rows, andindexBy(),groupBy(), andcolumn()kept them under renumbered integer keys:$schema = SmartArray::new([ 'menuName' => 'Products', // scalar setting 'name' => ['type' => 'textfield', 'order' => 2], 'photo' => ['type' => 'upload', 'order' => 1], ]); $schema->where('type', 'upload'); // rows whose type matches; the scalar is ignored $schema->indexBy('type'); // v2.x: scalars kept under renumbered keys; v3.0: rows onlyDatabase results are unaffected - every element is a row. Review only hand-built arrays that mix scalars and rows: a scalar that used to reach the result through
sortBy(),indexBy(),groupBy(), orcolumn()is left out now.
print_r()andvar_dump()show just the array data, like dumping a plain array - the injected pseudo-entries (the README help pointer and theuseSmartStringsflag) are gone. Use->debug()for exact types and metadata.indexBy()keys rows missing the index field under""(same as rows where the field is null) instead of leaving a numeric key that looked like a real field value. Duplicates still last-wins.sortBy()sorts rows missing the sort field first, like MySQL ORDER BY sorts nulls, instead of throwingValueError: Array sizes are inconsistent.set(),->key = $value, andget()defaults now accept Smart values (SmartString, SmartArray, SmartNull) - they unwrap and re-wrap for the array's mode instead of throwing, so values copy between arrays without calling->value()first. Only affects code that relied on those throws.load()throwsInvalidArgumentExceptioninstead ofRuntimeExceptionwhen the field name contains invalid characters, matching its empty-field check. Only affects code catchingRuntimeExceptionaroundload().- Writes to a
SmartNull(a missing key or empty result) throwRuntimeExceptioninstead of silently discarding the value.- Raw-mode arrays throw on SmartString-style fallbacks like
->or()on a missing key - use??instead.orDie()andor404()exit with status 1 instead of 0, so shell scripts and cron jobs see the failure.
No required changes: the old names still work with no runtime notice, and IDEs like PHPStorm show them in strikethrough with a one-click rename.
| Old name (still works) | Current name |
|---|---|
->nth($n) |
->at($n) |
->pluckNth($n) |
->columnAt($n) |
->pluck($field) |
->column($field) |
->get($key, $default) |
$row->key, $row->key ?? $default |
->set($key, $value) |
$row->key = $value |
->each($fn) |
a plain foreach loop |
->sprintf($format) |
->map(fn($v) => "<li>$v</li>") |
->help() |
the docs on GitHub |
sortBy(type: ...) |
sortBy(flags: ...) (named-argument calls only) |
Follow this section when upgrading from SmartArray before v2.7.0 (or CMS Builder before 3.85).
PHP lets you write a parameter's name right in the call - the
text:part in->orDie(text: 'Not found'). If you never do this, skip this check. If you do, one parameter name changed, and calls using the old name fail with a clear "Unknown named parameter" Error:->orDie('Not found') // no parameter name - nothing changes ->orDie(message: 'Not found') // before (same for or404, orThrow) ->orDie(text: 'Not found') // afterFix:
- Search
message:and replace withtext:on or404/orDie/orThrow callsRegex:
->(orDie|or404|orThrow)\(\s*message:
json_encode($smartArray)substitutes malformed UTF-8 bytes with � (U+FFFD) instead of returning false - one corrupt byte no longer breaks the whole page, and code checking for a false return no longer sees one
Follow this section when upgrading from SmartArray before v2.6.7 (or CMS Builder before 3.83).
SmartArrayHtml used to extend SmartArray, so it passed
SmartArraytype hints. Both classes are now siblings underSmartArrayBase, so passing an HTML-mode array to aSmartArrayhint is a fatal TypeError naming your function:function formatRows(SmartArray $rows): SmartArray { ... } // TypeError when passed SmartArrayHtml function formatRows(SmartArrayBase $rows): SmartArrayBase { ... } // works for both modesFix:
- Search
SmartArrayin parameter and return types; useSmartArrayBasewherever either mode can arrive
Bracket access still works but is deprecated, and by default it now echoes a visible "Deprecated:" notice into the page in addition to
trigger_error(). Each notice names your file and line:echo $row['name']; // works, but prints a Deprecated: notice into the page echo $row->name; // correct echo $row->{'users.id'}; // correct - for keys property syntax can't typeFix:
- Follow the file and line in each notice and switch to
->keyor->{'key'}- Sites mid-migration can silence the echo (your error handler still receives the notices):
SmartArrayBase::$onOffsetAccess = 'log';
All three settings are gone, and leftovers fail loudly ("Access to undeclared static property") - if your pages load, you're clean.
Fix:
- Search
$warnIfMissing,$warnIfDeprecated, and$logDeprecations- remove them; deprecation notices always trigger now, and error handlers decide what to show
No required changes: the old names still work and raise a deprecation notice naming their replacement (visible in error handlers like CMS Builder's developer log). Renaming is optional cleanup.
| Old name (still works) | Current name |
|---|---|
->toRaw() |
->asRaw() |
->toHtml() |
->asHtml() |
->smartMap() |
->map() |
SmartArrayRaw class |
SmartArray |
->chunk() |
deprecated, no replacement planned |
->isMultipleOf($n) |
->position() % $n === 0 |
->where([...]) array form |
chained ->where($field, $value) calls |
Follow this section when upgrading from SmartArray before v2.4.0 (or CMS Builder before 3.80). Requires PHP 8.1+.
where()matches loosely (==, so"5"matches 5) instead of strictly - rows that fell out of results because a database value was a string and the condition was a number (or vice versa) now match. Reviewwhere()calls only if you relied on that type mismatch to exclude rows.enableSmartStrings()anddisableSmartStrings()are deprecated - use->asHtml()and->asRaw()(the old names still work and raise a notice)
Follow this section when upgrading from SmartArray before v2.0.1 (or CMS Builder before 3.75).
Before v2.0, every value came back as a SmartString and HTML-encoded itself on output. Creation now returns raw PHP values, so a template echoing them outputs unencoded data:
$user = SmartArray::new(['name' => "Jean O'Brien <script>"]); echo $user->name; // unencoded - fine for data processing, not for HTML output $user = SmartArrayHtml::new(['name' => "Jean O'Brien <script>"]); echo $user->name; // HTML-encoded (v2.0 spelled this SmartArray::newSS())Fix:
- Search
new SmartArray(andSmartArray::new(- anywhere the values are echoed into HTML, create withSmartArrayHtml::new()instead- ZenDB query results are unaffected: they already come back HTML-safe
End of upgrade notes. There is nothing older to check: SmartArray was first bundled with CMS Builder v3.74 (as v1.2.0), and v1.x needs only the sections above.