Skip to content

Commit d926f07

Browse files
committed
docs: document edge-case behavior found in test suite review
- contains() and unique() compare loosely - add the surprising cases to the docblocks ('1' matches 1 and true; '', false, and null count as duplicates) - sortBy() reindexes numeric keys but keeps string keys (array_multisort default) - docs previously said all keys get reindexed - json_encode() returns raw values, never HTML-encoded, even for SmartArrayHtml - orRedirect() throws whenever headers are already sent, even with results - make the fail-fast explicit in the docblock - debug() calls the load handler once per key - flag it so nobody debugs a row in a loop with a live database handler - SmartNull is truthy and == '' - note how to check for it explicitly - Reword load() error when the handler returns false: "doesn't support field" instead of "not available" (the handler exists and ran; it declined) - CHANGELOG: isFirst()/isLast()/position() aren't deprecated - they were promoted back to first-class in 2.6.2; drop them from the stale deprecation line
1 parent abd1578 commit d926f07

6 files changed

Lines changed: 31 additions & 9 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
### Deprecated
5656
- **Array access syntax**: `$array['key']` is deprecated - use `$array->key` or `$array->get('key')` instead
5757
- `SmartArrayRaw` class - now an alias for `SmartArray`, use `SmartArray` directly
58-
- `isFirst()`, `isLast()`, `position()`, `isMultipleOf()`, `chunk()`, `smartMap()` - now trigger deprecation notice
58+
- `isMultipleOf()`, `chunk()`, `smartMap()` - now trigger deprecation notice (isFirst/isLast/position were promoted back to first-class methods in 2.6.2)
5959
- `toRaw()` and `toHtml()` - use `asRaw()` and `asHtml()` instead
6060

6161
### Fixed

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,12 +518,12 @@ Note: All methods return a new `SmartArray` object unless otherwise specified.
518518
| Array Information | count() | Get the number of elements |
519519
| | isEmpty() | Returns true if array has no elements |
520520
| | isNotEmpty() | Returns true if array has any elements |
521-
| | contains(value) | Returns true if array contains value |
521+
| | contains(value) | Returns true if array contains value (loose == comparison) |
522522
| Position & Layout | isFirst() | Returns true if first element in parent array |
523523
| | isLast() | Returns true if last element in parent array |
524524
| | position() | Gets position in parent array (starting from 1) |
525525
| Sorting & Filtering | sort() | Sorts elements by value (flat arrays only) |
526-
| | sortBy(field) | Sorts rows by field value (nested arrays only) |
526+
| | sortBy(field) | Sorts rows by field value (nested arrays only; numeric keys reindexed, string keys preserved) |
527527
| | unique() | Removes duplicate values (flat arrays only) |
528528
| | filter() | Removes falsey values ("", 0, empty array, etc) |
529529
| | filter(callback) | Removes elements where callback returns false (callback receives raw values) |

‎src/SmartArrayBase.php‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,11 @@ public function isNotEmpty(): bool
433433
}
434434

435435
/**
436-
* Check if array contains a specific value (loose comparison).
436+
* Check if array contains a specific value (loose == comparison).
437+
*
438+
* Loose comparison means types don't need to match: contains('1') matches
439+
* 1 and true, and contains(null) matches '' and false. For strict matching
440+
* use in_array($value, $arr->toArray(), true).
437441
*/
438442
public function contains(mixed $value): bool
439443
{
@@ -459,6 +463,9 @@ public function sort(int $flags = SORT_REGULAR): static
459463
/**
460464
* Returns a new SmartArray sorted ascending by the specified field.
461465
* Only works on nested arrays (throws on flat).
466+
*
467+
* Numeric row keys are re-indexed; string keys are preserved
468+
* (array_multisort() default behavior).
462469
*/
463470
public function sortBy(string $field, int $type = SORT_REGULAR): static
464471
{
@@ -477,6 +484,9 @@ public function sortBy(string $field, int $type = SORT_REGULAR): static
477484
* Returns a new SmartArray with duplicate values removed, keeping only the first
478485
* occurrence of each unique value, and preserving keys.
479486
* Only works on flat arrays (throws on nested).
487+
*
488+
* Values are compared as strings (array_unique() default): 1, '1', and true
489+
* count as duplicates; '', false, and null count as duplicates of each other.
480490
*/
481491
public function unique(): static
482492
{
@@ -1046,7 +1056,7 @@ public function load(string $field): static|SmartNull
10461056
// get handler output
10471057
$result = $loadHandler($this, $field);
10481058
if ($result === false) {
1049-
throw new Error("Load handler not available for '$field'\n" . self::occurredInFile());
1059+
throw new Error("Load handler doesn't support field '$field'\n" . self::occurredInFile());
10501060
}
10511061

10521062
// output error checking
@@ -1111,6 +1121,9 @@ public function help(): void
11111121
/**
11121122
* Displays diagnostic output: array contents, mysqli metadata, and object properties.
11131123
*
1124+
* If a load handler is set, calls it for every key to annotate loadable
1125+
* relations - with a database-backed handler this can run one query per key.
1126+
*
11141127
* @param int $debugLevel 0 for compact, 1+ for verbose with type info and object IDs
11151128
*/
11161129
public function debug(int $debugLevel = 0): void
@@ -1379,7 +1392,9 @@ public function orThrow(string $text): static
13791392
* Redirects to a URL if the array is empty
13801393
*
13811394
* Uses a simple Location header redirect (HTTP 302 Temporary Redirect).
1382-
* If headers have already been sent, this method will throw an exception.
1395+
* If headers have already been sent, throws immediately - even when the
1396+
* array is not empty - so a misplaced call fails on every request, not
1397+
* just when a result happens to be empty.
13831398
*
13841399
* @param string $url The URL to redirect to if array is empty
13851400
* @return static Returns $this for method chaining if not empty, redirects if empty
@@ -1561,6 +1576,9 @@ public function getIterator(): Iterator
15611576
* Returns serializable data for `json_encode()` via JsonSerializable.
15621577
* Returns the raw internal array so nested SmartArrays serialize as plain arrays.
15631578
*
1579+
* Values are raw, not HTML-encoded, even for SmartArrayHtml: JSON is a data
1580+
* format, and HTML encoding applies only when values are output as HTML.
1581+
*
15641582
* Substitutes malformed UTF-8 with � (U+FFFD) so json_encode($smartArray) returns valid JSON
15651583
* instead of false. Nested SmartArrays scrub themselves when json_encode() descends into them.
15661584
*

‎src/SmartNull.php‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*
1616
* Implements SmartBase so instanceof SmartBase works for all Smart* types.
1717
* Extends stdClass to avoid IDE warnings related to undefined properties.
18+
*
19+
* Note: as an object, SmartNull is truthy in conditionals and compares loosely
20+
* equal to '' via __toString ($smartNull == '' is true). For explicit checks
21+
* use ->value() === null or instanceof SmartNull.
1822
*/
1923
class SmartNull extends stdClass implements SmartBase, Iterator, ArrayAccess, JsonSerializable, Countable
2024
{

‎src/help.txt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Array Information
6262
->count() Get the number of elements
6363
->isEmpty() Returns true if array has no elements
6464
->isNotEmpty() Returns true if array has any elements
65-
->contains(value) Check if array contains a specific value
65+
->contains(value) Check if array contains a specific value (loose == comparison)
6666

6767
Position & Layout
6868
------------------
@@ -73,7 +73,7 @@ Position & Layout
7373
Sorting & Filtering
7474
--------------------
7575
->sort() Sort elements by value, reindexing keys
76-
->sortBy(field) Sort nested array by field value, reindexing keys
76+
->sortBy(field) Sort nested array by field value (numeric keys reindexed, string keys preserved)
7777
->sortBy(field, type) Sort with type: SORT_STRING, SORT_NUMERIC, SORT_REGULAR (default)
7878
->unique() Remove duplicate values, keeping first occurrence
7979
->filter(callback) Keep elements where callback returns true, using raw values

‎tests/Methods/LoadTest.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public function testLoadThrowsWhenHandlerReturnsFalse(): void
187187
$smartArray->setLoadHandler(fn($row, $col) => false);
188188

189189
$this->expectException(Error::class);
190-
$this->expectExceptionMessage("Load handler not available for 'products'");
190+
$this->expectExceptionMessage("Load handler doesn't support field 'products'");
191191

192192
$smartArray->load('products');
193193
}

0 commit comments

Comments
 (0)