Skip to content

Latest commit

 

History

History
417 lines (349 loc) · 26.4 KB

File metadata and controls

417 lines (349 loc) · 26.4 KB

SmartArray AI Reference

This is a consolidated reference for AI coding assistants. It contains everything needed to write correct SmartArray code in a single file, and covers SmartArray 3.0. For human-friendly docs with tutorials and explanations, see Getting Started on GitHub (this is the only docs file shipped in the Composer package, so links here are absolute).

Contents:


What is SmartArray

SmartArray wraps PHP arrays (usually database rows) in chainable collection methods. Two concrete classes share one API; the ONLY difference is what a field read returns:

  • SmartArrayHtml (HTML mode): fields return SmartString objects that HTML-encode in every string context. Use whenever output is a web page. ZenDB and CMS Builder query results arrive in this mode.
  • SmartArray (raw mode): fields return plain PHP values in their original types. Use for JSON, CSV, email, CLI, and data processing.
use Itools\SmartArray\SmartArray;
use Itools\SmartArray\SmartArrayHtml;

$users = SmartArrayHtml::new([
    ['name' => "Jean O'Brien", 'city' => 'Vancouver'],
    ['name' => 'Tom & Jerry Inc', 'city' => 'Ottawa'],
]);

foreach ($users as $user) {
    echo "<li>$user->name from $user->city</li>\n";  // <li>Jean O&apos;Brien from Vancouver</li>
}
echo $users->where('city', 'Ottawa')->first()->name;  // Tom &amp; Jerry Inc

Key definitions used throughout:

  • row = a nested SmartArray inside a parent collection (created automatically for nested input arrays). Rows know their position() (1-based); top-level and derived collections report position 0.
  • field = a scalar element read off a collection: SmartString in HTML mode, plain PHP value in raw mode. Data is stored raw and wrapped on access, never modified.
  • SmartNull = chainable placeholder returned for missing keys and empty lookups. Echoes as "", counts as 0, iterates as nothing; SmartArray and SmartString methods on it keep working.
  • Collection methods behave identically in both modes: callbacks, matching, and sorting always operate on original raw values. Methods returning a collection return it in the calling object's mode.
  • Transformation methods return NEW collections; the original is never modified.

Class Hierarchy and Type Hints

SmartBase (interface)     every collection type plus SmartNull (not SmartString)
├── SmartArrayBase        abstract base - type-hint this to accept either mode
│   ├── SmartArray            raw mode
│   └── SmartArrayHtml        HTML mode
└── SmartNull             returned for missing keys and empty lookups

SmartArrayHtml is NOT instanceof SmartArray; they are siblings. Hint SmartArrayBase for functions accepting either mode, SmartBase to also accept SmartNull.

Creating Collections

SmartArrayHtml::new(array $array = [], array $properties = []): SmartArrayHtml
SmartArray::new(array $array = [], array $properties = []): SmartArray
  • Nested arrays become child rows (recursively); scalars and null store as-is; Smart values unwrap; other objects/resources throw InvalidArgumentException.
  • $properties is for database layers: ['mysqli' => [...metadata...], 'loadHandler' => callable]. Normal code omits it.
  • new SmartArray($data) works too; ::new() exists because new SmartArray($data)->method() is a syntax error before PHP 8.4.

Reading and Writing Fields

$row->name                    // property syntax is canonical
$row->{'users.id'}            // braces for keys property syntax can't type (dots, dashes, numeric)
$row->{0}                     // numeric keys
$row->name = 'Jean';          // writes use the same syntax
$row->{'sort-order'} = 5;
  • HTML mode wraps scalar reads in SmartString; nested rows come back as collections (never wrapped). Raw mode returns everything as-is.
  • Writes unwrap Smart values (SmartString stores its raw value, SmartNull stores null, SmartArray children convert to the target's mode).
  • SmartString fields used inside braces stringify to their HTML-ENCODED output. Digits are unaffected, so numeric id keys work as-is; for text keys pass the original: $map->{$field->value()}.
  • isset($row->key) / empty($row->key) / $row->key ?? $default use plain-array semantics: stored NULL reads as missing (so ?? fires on missing keys AND stored NULLs) and none of them ever warn.
  • The ?? fallback is a plain value, output with NO encoding: keep ?? fallbacks to literals. For display fallbacks use the field's or(), which also covers "" and keeps the result encoded.
  • Empty-string keys exist but property syntax can't reach them; only the deprecated get('')/set('') can (and $arr[null] reads key '').
  • unset($row->key) removes a key.

Missing Keys and SmartNull

Reading a key that doesn't exist returns a SmartNull. Warning behavior depends on WHERE you read (changed in 3.0):

  • Rows inside a result set (position 1+): echoes Warning: keyname is undefined in file.php:LINE and triggers E_USER_WARNING (caller's file:line, key HTML-encoded, plus a wrap-methods-in-braces hint when the key matches a method name). Row keys are column names, so a miss is treated as a typo.
  • Everywhere else (top-level collections, indexBy()/column() lookup maps, standalone arrays, empty collections): silent. A miss is a normal no-match, so fallbacks chain cleanly: $authorById->{$id}->or('Unknown').

Separate rule for method ARGUMENTS: a field name passed to where(), whereNot(), whereInList(), sortBy(), indexBy(), groupBy(), or column() that doesn't exist in the first row warns from ANY nested collection, result set or not, with the format funcName(): 'field' doesn't exist (caller's file:line appended).

SmartNull behavior: echo""; value() → null; count() → 0; foreach iterates zero times; toArray()[]; json_encode() → null; SmartArray methods return empty results; SmartString methods (HTML mode) behave as on null, except transforms return the same SmartNull (chain stays missing, accepts value or collection endings) and map() skips its callback (a NULL value in an existing key still runs it); guards (or404() etc.) FIRE (empty = missing); one-argument set($value) produces that value in HTML mode only (raw mode throws like any write); all other writes throw RuntimeException ("Cannot set values on SmartNull"). It carries the source's mysqli metadata and load handler.

Iteration and Keys

  • foreach ($collection as $key => $value): values follow the mode (rows as collections, scalars as fields); keys are always raw plain values, never encoded, in both modes. When keys came from user data (groupBy() on a user-entered field) and get echoed, iterate $collection->keys() instead - keys() returns them as fields that encode on output.
  • Iteration order is insertion/array order. Nested rows yield as-is.
  • foreach over a SmartString field throws; over SmartNull yields nothing.

Mode Conversion and Plain Arrays

Method Returns
asHtml(): SmartArrayHtml Collection in HTML mode; same object if already HTML, else a new collection (original unchanged). Rows keep position metadata
asRaw(): SmartArray Collection in raw mode; same object if already raw
toArray(): array Plain nested PHP array, original values, both modes
SmartArray::getRawValue(mixed $value) (static) SmartString → value, SmartArray → array, SmartNull → null, scalar/null/array pass through (arrays unwrapped recursively); other objects throw InvalidArgumentException
  • json_encode($collection) (JsonSerializable) emits RAW original values in both modes (JSON is a data format; HTML encoding is output-only). Malformed UTF-8 in keys or values becomes � (U+FFFD) instead of returning false.
  • (array)$collection exposes internal object properties (PHP has no cast hook) - never use it; use toArray(). Spread [...$collection] works for flat lists (top level only) but keeps element mode: SmartString objects in HTML mode, plain values in raw mode. For plain original values, use toArray().

Single Elements

first(): row|field|SmartNull                               // first element
last(): row|field|SmartNull                                // last element
at(int|SmartString|SmartNull $index): row|field|SmartNull  // by position, ignoring keys: 0 first, -1 last

All three return SmartNull silently when there is no such element (empty collection, out-of-range index, SmartNull index).

Collection Checks

count(): int                  // also works via count($collection) (Countable)
isEmpty(): bool               // no elements
isNotEmpty(): bool            // any elements
contains(mixed $value): bool  // any element matches $value (where() rules; Smart args unwrap)

Field-level checks (isMissing(), isEmpty(), or(), ...) are SmartString methods, available on fields in HTML mode; see the SmartString AI reference.

Row Position

isFirst(): bool    // true for the first row in its parent collection
isLast(): bool     // true for the last row
position(): int    // 1-based position in parent; 0 on top-level/derived collections

Computed on first call and then kept; preserved through asHtml()/asRaw(). Rows in a DERIVED collection (a where() result) get fresh positions in the new collection.

Filtering and Sorting

All return a new collection. Methods marked "Rows only" work on the rows (elements that are arrays) and ignore other elements (scalars/null); a non-empty array with no rows throws InvalidArgumentException (empty arrays pass). Methods marked "Flat only" throw on nested input.

Method Behavior
where(string $field, mixed $value = null): static Rows only. Keeps rows where $field matches $value: strings match as exact text ('0e12' never matches '0e99'), numbers match numerically in either direction ('1' matches 1, 1 matches '1.00'), null matches only null (SQL IS NULL), bools compare as 1/0 on either side. Smart args unwrap. Rows without the field are dropped. Chain calls for AND. Warns when $field is missing from the first row. Single-arg where($field) keeps rows where the field is non-empty (PHP empty() rule: NULL, false, 0, "0", "", missing are empty). NOTE: where($f) and where($f, null) differ - the latter matches only stored NULLs
whereNot(string $field, mixed $value = null): static Rows only. Drops rows where $field matches $value (same matching rules as where()); rows WITHOUT the field are kept. Single-arg whereNot($field) keeps rows where the field is empty or missing (exact complement of where($field))
whereInList(string $field, mixed $value): static Rows only. Keeps rows where tab-separated $field contains $value as a whole value ("\tmenu\tfooter\t" format, CMS Builder checkbox/multi-select fields) or equals it as a plain single value. Never substring matching
filter(?callable $callback = null): static Both shapes. Callback receives raw ($value, $key), keeps on true. No callback: removes falsy ("", "0", 0, null, false). Keys preserved like array_filter() - chain values() for a clean JSON array
sort(int $flags = SORT_REGULAR): static Flat only. Sorts ascending by value, renumbers keys. $flags choose comparison only; SORT_ASC/SORT_DESC throw InvalidArgumentException (sort descending in SQL)
sortBy(string $field, int $flags = SORT_REGULAR): static Rows only. Ascending by $field; rows missing the field sort first (like MySQL ORDER BY) and are kept unchanged. Numeric row keys renumber, string keys preserved. SORT_NATURAL for human number order; SORT_ASC/SORT_DESC throw
unique(): static Flat only. Removes duplicates keeping the first, keys preserved; compares as strings (array_unique()), so 1 and '1' are duplicates

Transforming and Grouping

"Rows only" and "Flat only" carry the same contract as in Filtering and Sorting above.

Method Behavior
column(int|string|null $columnKey, int|string|null $indexKey = null): static Rows only. Like array_column(): one field per row; $indexKey keys results by another field using indexBy() rules (missing field keys under '', floats throw, bools key as 1/0); column(null, $indexKey) keys whole rows, same as indexBy()
columnAt(int $index): static Rows only. The column at a position from each row, ignoring key names (0 first, -1 last)
indexBy(string $field): static Rows only. Whole rows keyed by $field; duplicate keys keep the LAST row. Null/missing field keys under ''; floats throw (convert to strings first), booleans key as 1/0
groupBy(string $field): static Rows only. Rows grouped by $field: one child collection per distinct value; same keying rules as indexBy()
keys(): static The keys as a new collection (encode on output in HTML mode)
values(): static The values, keys renumbered from 0
map(callable $callback): static New collection from $callback per element: closures receive raw ($value, $key), PHP built-ins receive $value only; rows arrive as plain arrays; returned arrays become rows again
merge(array|SmartArrayBase|SmartNull ...$arrays): static Appends: numeric keys renumber, string keys overwrite (later wins); SmartNull merges as empty
implode(string $separator = ''): SmartString|string Flat only. Joins values; returns SmartString in HTML mode (encodes on output), plain string in raw mode

Guards

Fire when the COLLECTION IS EMPTY (no rows/elements; contrast SmartString's field guards, which fire on missing values). Non-empty: return $this unchanged, so they chain inline. $text is HTML-encoded automatically (messages often interpolate user input).

Method On empty
or404(?string $text = null): static HTTP 404 + minimal HTML page + exit(1). Default text "The requested URL was not found on this server."
orDie(string $text): static Echo encoded text + exit(1)
orThrow(string $text): static throw new RuntimeException($encodedText)
orRedirect(string $url): static 302 + Location: $url + exit. Checks headers_sent() immediately (throws even when non-empty)
$article = $articles->where('num', $num)->first()->or404('Article not found');

(first() on empty returns SmartNull; its or404() delegates and fires.)

Database Metadata

Set via the $properties constructor argument; ZenDB and CMS Builder do this automatically.

  • mysqli(?string $property = null): int|string|null|array - all metadata as an array with no argument ([] when none), or one value by name ('affected_rows', 'insert_id', 'query', 'baseTable', ...); unknown or unset properties return null.
  • load(string $field): static|SmartNull - loads related records for $field via the configured loadHandler. Returns SmartNull when the collection is empty; throws RuntimeException when no handler is set or when called on a record set (call it on a row). Handler contract: return [rows, mysqliProperties], or false to reject the field, which throws a PHP native Error naming it. Any other return throws a PHP native Error describing what came back instead.

Debugging

$collection->debug();   // contents, current mode, mysqli metadata; debug(1) adds types and internals
print_r($collection);   // element data only; the class name identifies the mode

debug() output is <xmp>-wrapped in the browser and plain text on the command line. PHP's print_r()/var_dump() output raw values with no wrapper - in a browser use debug().

Errors and Exceptions

  • InvalidArgumentException: unsupported value types in constructor or writes (objects/resources), flat/nested shape mismatches (sort() on nested, where() on flat), SORT_ASC/SORT_DESC passed to sort()/sortBy(), getRawValue() on unsupported objects, invalid load() field names.
  • RuntimeException: orThrow() (message HTML-encoded), orRedirect() with headers already sent, writes to SmartNull, load() without a handler or called on a record set.
  • Error (PHP native): undefined method calls (caller's file:line, plus a did-you-mean suggestion when the name matches a known old method, otherwise a docs pointer); load() handler returning anything but [rows, mysqliProperties], including false.
  • TypeError: a strict callback passed to map() or filter() throws PHP's own TypeError when an element doesn't match its signature (e.g. strtoupper(...) on a null or int element).
  • E_USER_WARNING (echoed + trigger_error): missing key on a result-set row; missing field-name argument to where()/sortBy()/indexBy() and friends (any nested collection, see Missing Keys above); string conversion of a collection (echo "$users" yields "", page continues, message suggests "{$var->method()}" braces).
  • E_USER_DEPRECATED: $arr['key'] array syntax, and deprecated names that have reached the notice stage (see below).

Deprecated Names

Old names still work. Some log deprecation notices naming the replacement; the rest are flagged only by IDEs and static analysis, with no runtime signal. When reading old code, translate:

Deprecated Use instead
$arr['key'], $arr['key'] = $v (array syntax) $arr->key, $arr->key = $v (braces for odd keys)
get($key) / get($key, $default) ->key / ->{'key'}; for defaults use ->or($default) in HTML mode (encoded; ?? fallbacks skip encoding) or ?? $default in raw mode (NOTE: both fire on stored NULLs too; get()'s default only fired on missing keys)
set($key, $value) ->key = $value / ->{'key'} = $value
pluck($field) / pluck($field, $keyField) column($field) / column($field, $keyField)
pluckNth($index) columnAt($index)
nth($index) at($index)
toRaw(), noSmartStrings(), disableSmartStrings() asRaw()
toHtml(), withSmartStrings(), enableSmartStrings() asHtml() or SmartArrayHtml::new()
smartMap($callback) map($callback)
each($callback) a foreach loop
sprintf($format) map() with an inline format string
where(['field' => $value, ...]) (array arg) chained where('field', $value) calls
isMultipleOf($n) ->position() % $n === 0
chunk($size) deprecated, no replacement planned
help() retired; read the docs on GitHub
SmartArrayRaw, SmartArrayRaw::new() SmartArray, SmartArray::new() (it extends SmartArray, so instanceof checks still pass)

How the deprecated array syntax is reported is configurable via SmartArrayBase::$onOffsetAccess: 'notify' (default) echoes a notice into the page and passes it to your error handler, 'log' passes it to your error handler only (for legacy sites mid-migration), 'throw' throws a RuntimeException (strict mode for new installs).

Gotchas Quick Reference

  • SmartArrayHtml is not instanceof SmartArray; type-hint SmartArrayBase to accept both.
  • Foreach KEYS are never encoded, even in HTML mode; output keys via keys() or encode manually.
  • Comparisons on HTML-mode fields compare the object: unwrap with ->value()/->int() first. empty($row->field) is false for stored ""/0 (objects are truthy); use $row->field->isEmpty().
  • A stored NULL is not PHP null in HTML mode ($row->x === null is false); use ->value() === null or ->isMissing(). Raw mode returns real null.
  • Braces stringify SmartString keys HTML-encoded: $map->{$field} misses on text with '/&; use $map->{$field->value()}. Numeric ids are safe.
  • (array)$collection returns internal object data; use toArray().
  • filter() keeps keys; json_encode() of a gapped array is an object, not an array - chain values().
  • Collection guards fire on EMPTY collections; SmartString field guards fire on missing VALUES. $row->or404() (row) vs $row->num->or404() (field) differ.
  • where() drops rows missing the field; whereNot() keeps them.
  • implode() in HTML mode returns a SmartString: interpolating it into a raw-SQL string would encode the joined text; call ->string() first or use raw mode for SQL.
  • Missing-key READS warn only on result-set rows; standalone lookups are silent by design. A missing field-name ARGUMENT (where(), sortBy(), indexBy(), ...) warns from any nested collection.
  • In raw mode a MISSING field returns SmartNull, an object, and objects are always truthy: bare if ($user->is_admin) runs when the field is absent or misspelled. PHP's isset() and ?? see missing keys correctly; use them or ->value() for logic.
  • HTML encoding makes values safe as HTML text and quoted attribute values only. It does NOT make them safe as javascript:-scheme hrefs (check the scheme first), inside <script> or <style> blocks (use jsonEncode()), or as URL query parameters (use urlEncode(), which returns "" on a NULL field where urlencode($x->value()) raises a deprecation; for a path segment use rawurlencode($x->value())).
  • echo $collection / "$users" never works (collections have no string form); echo fields or implode().

← Documentation Index | ← Prev: Performance