Passing money as an integer means every function taking it has to validate it, and every one of those validations needs a test. Passing a type that cannot be constructed invalidly moves all of that to one place.
final class Money
{
private $cents;
private $currency;
public function __construct(int $cents, string $currency)
{
if ($cents < 0) {
throw new InvalidArgumentException('negative');
}
$this->cents = $cents;
$this->currency = $currency;
}
public function add(Money $other): Money
{
$this->assertSameCurrency($other);
return new Money($this->cents + $other->cents, $this->currency);
}
}
Immutability is what makes it safe to pass around — add returning a new instance means nothing holding a reference is surprised. The currency check inside add catches the bug that integer arithmetic cannot: adding pounds to euros. The objection is always that it is more code, and the answer is that it is less code than the validation and the tests it replaces, distributed across the twelve places that handled a raw integer.