public members are reachable everywhere, protected ones from the class and its subclasses, and private ones from the declaring class only; methods default to public. Visibility is checked per class, not per object, so one Money may read another's private $cents. Violations throw a catchable Error.
<?php
class Money {
public function __construct(private int $cents, private string $cur = 'USD') {}
public function add(Money $o): static {
if ($o->cur !== $this->cur) { $this->fail("Cannot add $o->cur to $this->cur"); }
return new static($this->cents + $o->cents, $this->cur); // reads $o's privates
}
public function text(): string { return sprintf('%s %.2f', $this->cur, $this->cents / 100); }
private function fail(string $why): never { throw new InvalidArgumentException($why); }
}
$total = new Money(1999)->add(new Money(501));
echo $total->text(), "\n";
foreach ([fn() => $total->cents, fn() => $total->fail('x'),
fn() => $total->add(new Money(1, 'EUR'))] as $f) {
try { $f(); } catch (Throwable $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; }
}Output
USD 25.00 Error: Cannot access private property Money::$cents Error: Call to private method Money::fail() from global scope InvalidArgumentException: Cannot add EUR to USD
Keep state private and expose behavior. When outside code must read a value, PHP 8.4's public private(set) makes it readable everywhere but writable only inside (Subsection 4.11.10).