ArrayAccess makes [] work on an object through four methods: offsetExists() for isset() and ??, offsetGet(), offsetSet() (with a null offset for $obj[] = ...) and offsetUnset(). Unlike a plain array, the object can validate every key and value.
<?php
final class Stock implements ArrayAccess {
private array $qty = [];
public function offsetExists(mixed $sku): bool { return isset($this->qty[$sku]); }
public function offsetGet(mixed $sku): int { return $this->qty[$sku] ?? 0; }
public function offsetSet(mixed $sku, mixed $n): void {
if (!preg_match('/^BK-/', (string) $sku)) throw new InvalidArgumentException('bad SKU');
$this->qty[$sku] = max(0, (int) $n);
}
public function offsetUnset(mixed $sku): void { unset($this->qty[$sku]); }
}
$stock = new Stock();
$stock['BK-PHP-01'] = 12;
$stock['BK-PHP-01'] += 3; // offsetGet, then offsetSet: works
$stock['BK-PHP-01']--; // ++ and -- never call offsetSet
echo $stock['BK-PHP-01'], ' ', var_export(isset($stock['BK-SQL-01']), true), "\n";
try { $stock[] = 5; } catch (InvalidArgumentException $e) { echo $e->getMessage(), "\n"; }Output
PHP Notice: Indirect modification of overloaded element of Stock has no effect in /tmp/ch04-10/shop.php on line 15 15 false bad SKU
A compound assignment works because PHP reads through offsetGet() and writes back through offsetSet(). Increment and decrement, like nested writes into an array that offsetGet() returns, modify a copy instead, so PHP issues the notice and the stock stays at 15. ArrayObject is a ready-made wrapper with all three interfaces; PHP 8.5 deprecates giving it an object as storage.