__toString() runs wherever an object is used as a string and implies Stringable (PHP 8.0). __invoke() makes an object callable, like a configurable closure. Arithmetic on objects throws.
<?php
final class Money {
public function __construct(private int $cents, public string $cur = 'USD') {}
public function __toString(): string { // implies Stringable
return $this->cur . ' ' . number_format($this->cents / 100, 2);
}
}
final class AddTax {
public function __construct(private float $rate) {}
public function __invoke(int $cents): int { return (int) round($cents * (1 + $this->rate)); }
}
$price = new Money(123456);
echo "Total: $price | ", var_export($price instanceof Stringable, true), "\n";
echo implode(',', array_map(new AddTax(0.08), [1000, 2500])), "\n"; // object as callable
echo json_encode(array_keys((array) $price)), ' ', json_encode($price), "\n";
try { echo 1 + $price; } catch (TypeError $e) { echo $e->getMessage(), "\n"; }Output
Total: USD 1,234.56 | true
1080,2700
["\u0000Money\u0000cents","cur"] {"cur":"USD"}
Unsupported operand types: int + Money(array) mangles private keys as "\0Money\0cents" (protected: "\0*\0"), and json_encode() sees public properties only; JsonSerializable (Subsection 4.10.6) and __debugInfo() let you choose what JSON and var_dump() show.