PHP calls __get(), __set(), __isset() and __unset() when code touches an inaccessible or undeclared property, and __call() / __callStatic() for missing methods. They suit data whose shape is known only at run time, such as the products table's JSON attributes column (MySQL).
<?php
class Product {
public function __construct(public string $sku, private array $attrs) {}
public function __get(string $n): mixed {
return $this->attrs[$n] ?? throw new LogicException("No attribute '$n' on $this->sku");
}
public function __set(string $n, mixed $v): void { $this->attrs[$n] = $v; }
public function __isset(string $n): bool { return isset($this->attrs[$n]); }
public function __unset(string $n): void { unset($this->attrs[$n]); }
public function __call(string $name, array $args): mixed { // getPages() and friends
return str_starts_with($name, 'get') ? $this->__get(lcfirst(substr($name, 3)))
: throw new BadMethodCallException("Call to undefined method Product::$name()");
}
}
$p = new Product('BK-101', json_decode('{"pages": 412, "format": "paperback"}', true));
$p->language = 'en'; // __set
unset($p->format); // __unset
echo $p->pages, ' ', $p->getLanguage(), ' ', var_export(isset($p->format), true), "\n";
try { $p->discount(); } catch (BadMethodCallException $e) { echo $e->getMessage(), "\n"; }Output
412 en false Call to undefined method Product::discount()
Magic access is invisible to IDEs and analyzers, and slow: five million __get() reads took 249 ms here against 43 ms for a declared property. For computed or validated properties, prefer PHP 8.4's property hooks (Subsection 4.11.7).