Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Currency
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
5 / 5
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 code
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 minorUnits
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 equals
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace App\Shared\Domain\ValueObject;
6
7use InvalidArgumentException;
8use Symfony\Component\Intl\Currencies;
9
10final class Currency
11{
12    public function __construct(private readonly string $code)
13    {
14        // Format guard first (precise message for malformed input), then real
15        // ISO-4217 existence via ICU — rejects well-formed but non-existent codes.
16        if (preg_match('/^[A-Z]{3}$/', $code) !== 1 || !Currencies::exists($code)) {
17            throw new InvalidArgumentException(sprintf('Invalid ISO-4217 currency code <%s>.', $code));
18        }
19    }
20
21    public function __toString(): string
22    {
23        return $this->code;
24    }
25
26    public function code(): string
27    {
28        return $this->code;
29    }
30
31    /**
32     * ISO-4217 minor-unit exponent (decimal places), from ICU/CLDR via Symfony Intl.
33     * The code is guaranteed to exist (validated in the constructor).
34     */
35    public function minorUnits(): int
36    {
37        return Currencies::getFractionDigits($this->code);
38    }
39
40    public function equals(self $other): bool
41    {
42        return $this->code === $other->code;
43    }
44}