ArrayAccess

ArrayAccess and Array-Like Objects

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.

A stock table that validates SKUs and quantitiesPHP
<?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.