From 04010b7507f1b3b85711da205ae32472d97e1b9f Mon Sep 17 00:00:00 2001 From: tigitz Date: Mon, 21 Oct 2024 12:05:28 +0200 Subject: [PATCH] Allow extending Money with custom currency provider --- CHANGELOG.md | 19 ++ README.md | 42 ++-- src/AbstractMoney.php | 7 +- src/Context/CashContext.php | 1 + src/Context/CustomContext.php | 1 + src/Context/DefaultContext.php | 1 + src/Currency.php | 174 +--------------- src/CurrencyConverter.php | 14 +- src/CurrencyProvider.php | 15 ++ src/Exception/MoneyMismatchException.php | 5 +- ...on.php => UnknownIsoCurrencyException.php} | 17 +- src/IsoCurrency.php | 186 ++++++++++++++++++ ...cyProvider.php => IsoCurrencyProvider.php} | 117 +++++++---- src/Money.php | 21 +- src/MoneyBag.php | 12 +- src/MoneyComparator.php | 4 +- src/RationalMoney.php | 4 +- tests/AbstractTestCase.php | 6 +- tests/Context/AutoContextTest.php | 4 +- tests/Context/CashContextTest.php | 4 +- tests/Context/CustomContextTest.php | 4 +- tests/Context/DefaultContextTest.php | 4 +- tests/CurrencyConverterTest.php | 62 +++--- tests/CustomCurrency.php | 33 ++++ tests/ISOCurrencyProviderTest.php | 28 +-- .../{CurrencyTest.php => IsoCurrencyTest.php} | 48 ++--- tests/MoneyBagTest.php | 8 +- tests/MoneyTest.php | 5 +- tests/RationalMoneyTest.php | 4 +- 29 files changed, 505 insertions(+), 345 deletions(-) create mode 100644 src/CurrencyProvider.php rename src/Exception/{UnknownCurrencyException.php => UnknownIsoCurrencyException.php} (52%) create mode 100644 src/IsoCurrency.php rename src/{ISOCurrencyProvider.php => IsoCurrencyProvider.php} (51%) create mode 100644 tests/CustomCurrency.php rename tests/{CurrencyTest.php => IsoCurrencyTest.php} (66%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e50972..43948cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.11.0](https://github.com/brick/money/releases/tag/0.11.0) +💥 **Breaking changes** + +This release tackles significant issues ([#30](https://github.com/brick/money/issues/30), [#61](https://github.com/brick/money/issues/61)) identified by the community to enhance the flexibility of handling custom currencies. + +The update supports custom Money types extending the `Money` class, such as CryptoMoney and HistoricalIsoMoney, necessitating careful refactoring of the library's core concepts. For a comprehensive understanding of the design decisions and examples, please refer to the related pull requests. + +- `Currency` has been renamed to `IsoCurrency` to better reflect its role in representing ISO 4217 currencies +- `Currency` is now an interface, with `IsoCurrency` serving as its concrete implementation +- `Currency#getCurrencyCode()` is now `Currency#getCode()` +- `UnknownCurrencyException` has been renamed to `UnknownIsoCurrencyException` to align with its specific use for `IsoCurrency` types +- `ISOCurrencyProvider` has been renamed to `IsoCurrencyProvider` to adhere to the [upcoming PSR rules for capitalization of abbreviations](https://github.com/php-fig/per-coding-stylestyle/issues/95) + +Previously, allowing `string|int` as currency necessitated objects to identify the corresponding IsoCurrency, creating a tight coupling with a specific IsoCurrencyProvider implementation. To move towards a more currency-agnostic architecture, it is now advised to resolve the currency beforehand to ensure the correct currency is utilized. + +- The parameter `$currency` in `Brick\Money\Currency#is()` has been refined to accept only `Brick\Money\Currency`, removing support for `string|int` +- The parameter `$currency` in `Brick\Money\CurrencyConverter#convert()` has been refined to accept only `Brick\Money\Currency`, removing support for `string|int` +- The parameter `$currency` in `Brick\Money\CurrencyConverter#convertToRational()` has been refined to accept only `Brick\Money\Currency`, removing support for `string|int` + ## [0.10.0](https://github.com/brick/money/releases/tag/0.10.0) - 2024-10-12 💥 **ISO currency changes** diff --git a/README.md b/README.md index dc6a616..9a7ca80 100644 --- a/README.md +++ b/README.md @@ -352,24 +352,44 @@ Writing your own provider is easy: the `ExchangeRateProvider` interface has just ## Custom currencies -Money supports ISO 4217 currencies by default. You can also use custom currencies by creating a `Currency` instance. Let's create a Bitcoin currency: +Money supports ISO 4217 currencies by default. You can also use custom currencies by creating an object implementing a `Currency` interface. Let's create a Bitcoin currency: ```php -use Brick\Money\Currency; -use Brick\Money\Money; - -$bitcoin = new Currency( - 'XBT', // currency code - 0, // numeric currency code, useful when storing monies in a database; set to 0 if unused - 'Bitcoin', // currency name - 8 // default scale -); +class BtcCurrency implements Currency +{ + public function __toString(): string + { + return $this->getCode(); + } + + public function getCode(): string + { + return 'BTC'; + } + + public function getDefaultFractionDigits(): int + { + return 8; + } + + public function jsonSerialize(): mixed + { + return $this->getCode(); + } + + public function is(Currency $currency): bool + { + return $currency->getCode() === $this->getCode(); + } +} + +$bitcoinCurrency = new BtcCurrency(); ``` You can now use this Currency instead of a currency code: ```php -$money = Money::of('0.123', $bitcoin); // XBT 0.12300000 +$money = Money::of('0.123', $bitcoinCurrency); // BTC 0.12300000 ``` ## Formatting diff --git a/src/AbstractMoney.php b/src/AbstractMoney.php index daf9593..6eaba06 100644 --- a/src/AbstractMoney.php +++ b/src/AbstractMoney.php @@ -50,10 +50,15 @@ final public function to(Context $context, RoundingMode $roundingMode = Rounding final public function getAmounts() : array { return [ - $this->getCurrency()->getCurrencyCode() => $this->getAmount() + $this->getCurrency()->getCode() => $this->getAmount() ]; } + public static function getCurrencyProvider(): CurrencyProvider + { + return IsoCurrencyProvider::getInstance(); + } + /** * Returns the sign of this money. * diff --git a/src/Context/CashContext.php b/src/Context/CashContext.php index f8d6e46..27c051a 100644 --- a/src/Context/CashContext.php +++ b/src/Context/CashContext.php @@ -6,6 +6,7 @@ use Brick\Money\Context; use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Math\BigDecimal; use Brick\Math\BigNumber; diff --git a/src/Context/CustomContext.php b/src/Context/CustomContext.php index b7f545d..8b1767c 100644 --- a/src/Context/CustomContext.php +++ b/src/Context/CustomContext.php @@ -9,6 +9,7 @@ use Brick\Math\RoundingMode; use Brick\Money\Context; use Brick\Money\Currency; +use Brick\Money\IsoCurrency; /** * Adjusts a number to a custom scale, and optionally step. diff --git a/src/Context/DefaultContext.php b/src/Context/DefaultContext.php index 37d84c9..dd0d931 100644 --- a/src/Context/DefaultContext.php +++ b/src/Context/DefaultContext.php @@ -6,6 +6,7 @@ use Brick\Money\Context; use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Math\BigDecimal; use Brick\Math\BigNumber; diff --git a/src/Currency.php b/src/Currency.php index c8c6b1d..f7fe696 100644 --- a/src/Currency.php +++ b/src/Currency.php @@ -3,180 +3,14 @@ declare(strict_types=1); namespace Brick\Money; - -use Brick\Money\Exception\UnknownCurrencyException; use JsonSerializable; use Stringable; -/** - * A currency. This class is immutable. - */ -final class Currency implements Stringable, JsonSerializable +interface Currency extends Stringable, JsonSerializable { - /** - * The currency code. - * - * For ISO currencies this will be the 3-letter uppercase ISO 4217 currency code. - * For non ISO currencies no constraints are defined, but the code must be unique across an application, and must - * not conflict with ISO currency codes. - */ - private readonly string $currencyCode; - - /** - * The numeric currency code. - * - * For ISO currencies this will be the ISO 4217 numeric currency code, without leading zeros. - * For non ISO currencies no constraints are defined, but the code must be unique across an application, and must - * not conflict with ISO currency codes. - * - * If set to zero, the currency is considered to not have a numeric code. - * - * The numeric code can be useful when storing monies in a database. - */ - private readonly int $numericCode; - - /** - * The name of the currency. - * - * For ISO currencies this will be the official English name of the currency. - * For non ISO currencies no constraints are defined. - */ - private readonly string $name; - - /** - * The default number of fraction digits (typical scale) used with this currency. - * - * For example, the default number of fraction digits for the Euro is 2, while for the Japanese Yen it is 0. - * This cannot be a negative number. - */ - private readonly int $defaultFractionDigits; - - /** - * Class constructor. - * - * @param string $currencyCode The currency code. - * @param int $numericCode The numeric currency code. - * @param string $name The currency name. - * @param int $defaultFractionDigits The default number of fraction digits. - */ - public function __construct(string $currencyCode, int $numericCode, string $name, int $defaultFractionDigits) - { - if ($defaultFractionDigits < 0) { - throw new \InvalidArgumentException('The default fraction digits cannot be less than zero.'); - } - - $this->currencyCode = $currencyCode; - $this->numericCode = $numericCode; - $this->name = $name; - $this->defaultFractionDigits = $defaultFractionDigits; - } - - /** - * Returns a Currency instance matching the given ISO currency code. - * - * @param string|int $currencyCode The 3-letter or numeric ISO 4217 currency code. - * - * @throws UnknownCurrencyException If an unknown currency code is given. - */ - public static function of(string|int $currencyCode) : Currency - { - return ISOCurrencyProvider::getInstance()->getCurrency($currencyCode); - } - - /** - * Returns a Currency instance for the given ISO country code. - * - * @param string $countryCode The 2-letter ISO 3166-1 country code. - * - * @return Currency - * - * @throws UnknownCurrencyException If the country code is unknown, or there is no single currency for the country. - */ - public static function ofCountry(string $countryCode) : Currency - { - return ISOCurrencyProvider::getInstance()->getCurrencyForCountry($countryCode); - } - - /** - * Returns the currency code. - * - * For ISO currencies this will be the 3-letter uppercase ISO 4217 currency code. - * For non ISO currencies no constraints are defined. - * - * @return string - */ - public function getCurrencyCode() : string - { - return $this->currencyCode; - } - - /** - * Returns the numeric currency code. - * - * For ISO currencies this will be the ISO 4217 numeric currency code, without leading zeros. - * For non ISO currencies no constraints are defined. - * - * @return int - */ - public function getNumericCode() : int - { - return $this->numericCode; - } - - /** - * Returns the name of the currency. - * - * For ISO currencies this will be the official English name of the currency. - * For non ISO currencies no constraints are defined. - * - * @return string - */ - public function getName() : string - { - return $this->name; - } - - /** - * Returns the default number of fraction digits (typical scale) used with this currency. - * - * For example, the default number of fraction digits for the Euro is 2, while for the Japanese Yen it is 0. - * - * @return int - */ - public function getDefaultFractionDigits() : int - { - return $this->defaultFractionDigits; - } - - /** - * Returns whether this currency is equal to the given currency. - * - * The currencies are considered equal if their currency codes are equal. - * - * @param Currency|string|int $currency The Currency instance, currency code or numeric currency code. - * - * @return bool - */ - public function is(Currency|string|int $currency) : bool - { - if ($currency instanceof Currency) { - return $this->currencyCode === $currency->currencyCode; - } - - return $this->currencyCode === (string) $currency - || ($this->numericCode !== 0 && $this->numericCode === (int) $currency); - } + public function getCode(): string; - final public function jsonSerialize(): string - { - return $this->currencyCode; - } + public function getDefaultFractionDigits(): int; - /** - * Returns the currency code. - */ - public function __toString() : string - { - return $this->currencyCode; - } + public function is(Currency $currency) : bool; } diff --git a/src/CurrencyConverter.php b/src/CurrencyConverter.php index d02c989..0915b3c 100644 --- a/src/CurrencyConverter.php +++ b/src/CurrencyConverter.php @@ -33,7 +33,7 @@ public function __construct(ExchangeRateProvider $exchangeRateProvider) * Converts the given money to the given currency. * * @param MoneyContainer $moneyContainer The Money, RationalMoney or MoneyBag to convert. - * @param Currency|string|int $currency The Currency instance, ISO currency code or ISO numeric currency code. + * @param Currency $currency The Currency instance, ISO currency code or ISO numeric currency code. * @param Context|null $context A context to create the money in, or null to use the default. * @param RoundingMode $roundingMode The rounding mode, if necessary. * @@ -44,7 +44,7 @@ public function __construct(ExchangeRateProvider $exchangeRateProvider) */ public function convert( MoneyContainer $moneyContainer, - Currency|string|int $currency, + Currency $currency, ?Context $context = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY, ) : Money { @@ -57,19 +57,15 @@ public function convert( * Converts the given money to the given currency, and returns the result as a RationalMoney with no rounding. * * @param MoneyContainer $moneyContainer The Money, RationalMoney or MoneyBag to convert. - * @param Currency|string|int $currency The Currency instance, ISO currency code or ISO numeric currency code. + * @param Currency $currency The Currency instance * * @return RationalMoney * * @throws CurrencyConversionException If the exchange rate is not available. */ - public function convertToRational(MoneyContainer $moneyContainer, Currency|string|int $currency) : RationalMoney + public function convertToRational(MoneyContainer $moneyContainer, Currency $currency) : RationalMoney { - if (! $currency instanceof Currency) { - $currency = Currency::of($currency); - } - - $currencyCode = $currency->getCurrencyCode(); + $currencyCode = $currency->getCode(); $total = BigRational::zero(); diff --git a/src/CurrencyProvider.php b/src/CurrencyProvider.php new file mode 100644 index 0000000..4a372ff --- /dev/null +++ b/src/CurrencyProvider.php @@ -0,0 +1,15 @@ +getCurrencyCode(), - $actual->getCurrencyCode() + $expected->getCode(), + $actual->getCode() )); } diff --git a/src/Exception/UnknownCurrencyException.php b/src/Exception/UnknownIsoCurrencyException.php similarity index 52% rename from src/Exception/UnknownCurrencyException.php rename to src/Exception/UnknownIsoCurrencyException.php index bf7c265..96e764d 100644 --- a/src/Exception/UnknownCurrencyException.php +++ b/src/Exception/UnknownIsoCurrencyException.php @@ -7,11 +7,22 @@ /** * Exception thrown when attempting to create a Currency from an unknown currency code. */ -class UnknownCurrencyException extends MoneyException +class UnknownIsoCurrencyException extends MoneyException { - public static function unknownCurrency(string|int $currencyCode) : self + public static function unknownCurrency(?string $currencyCode = null, ?int $numericCode = null) : self { - return new self('Unknown currency code: ' . $currencyCode); + $baseMessage = 'Unknown currency '; + $messageParts = []; + if ($currencyCode !== null) { + $messageParts[] = 'code: ' . $currencyCode; + } + if ($numericCode !== null) { + $messageParts[] = 'numeric code: ' . $numericCode; + } + + $message = trim($baseMessage. implode(' and ', $messageParts)); + + return new self($message); } public static function noCurrencyForCountry(string $countryCode) : self diff --git a/src/IsoCurrency.php b/src/IsoCurrency.php new file mode 100644 index 0000000..c85aa03 --- /dev/null +++ b/src/IsoCurrency.php @@ -0,0 +1,186 @@ +hasCode($currencyCode, $numericCode)) { + throw UnknownIsoCurrencyException::unknownCurrency($currencyCode, $numericCode); + }; + + $this->currencyCode = $currencyCode; + $this->numericCode = $numericCode; + $this->name = $name; + $this->defaultFractionDigits = $defaultFractionDigits; + } + + /** + * Returns a Currency instance matching the given ISO currency code. + * + * @param string|int $currencyCode The 3-letter or numeric ISO 4217 currency code. + * + * @throws UnknownIsoCurrencyException If an unknown currency code is given. + */ + public static function of(string|int $currencyCode): self + { + return IsoCurrencyProvider::getInstance()->getByCode($currencyCode); + } + + /** + * Returns a Currency instance for the given ISO country code. + * + * @param string $countryCode The 2-letter ISO 3166-1 country code. + * + * @return IsoCurrency + * + * @throws UnknownIsoCurrencyException If the country code is unknown, or there is no single currency for the country. + */ + public static function ofCountry(string $countryCode): IsoCurrency + { + return IsoCurrencyProvider::getInstance()->getByCountryCode($countryCode); + } + + /** + * Returns the currency code. + * + * For ISO currencies this will be the 3-letter uppercase ISO 4217 currency code. + * For non ISO currencies no constraints are defined. + * + * @return string + */ + public function getCode(): string + { + return $this->currencyCode; + } + + /** + * Returns the numeric currency code. + * + * For ISO currencies this will be the ISO 4217 numeric currency code, without leading zeros. + * For non ISO currencies no constraints are defined. + * + * @return int + */ + public function getNumericCode(): int + { + return $this->numericCode; + } + + /** + * Returns the name of the currency. + * + * For ISO currencies this will be the official English name of the currency. + * For non ISO currencies no constraints are defined. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns the default number of fraction digits (typical scale) used with this currency. + * + * For example, the default number of fraction digits for the Euro is 2, while for the Japanese Yen it is 0. + * + * @return int + */ + public function getDefaultFractionDigits(): int + { + return $this->defaultFractionDigits; + } + + /** + * Returns whether this currency is equal to the given currency. + * + * The currencies are considered equal if their currency codes are equal. + * + * @param Currency $currency The Currency instance, currency code or numeric currency code. + * + * @return bool + */ + public function is(Currency $currency): bool + { + if ($currency instanceof IsoCurrency) { + return $this->currencyCode === $currency->currencyCode; + } + + return false; + } + + final public function jsonSerialize(): string + { + return $this->currencyCode; + } + + /** + * Returns the currency code. + */ + public function __toString(): string + { + return $this->currencyCode; + } +} diff --git a/src/ISOCurrencyProvider.php b/src/IsoCurrencyProvider.php similarity index 51% rename from src/ISOCurrencyProvider.php rename to src/IsoCurrencyProvider.php index c808f88..1cab3e8 100644 --- a/src/ISOCurrencyProvider.php +++ b/src/IsoCurrencyProvider.php @@ -4,14 +4,14 @@ namespace Brick\Money; -use Brick\Money\Exception\UnknownCurrencyException; +use Brick\Money\Exception\UnknownIsoCurrencyException; /** * Provides ISO 4217 currencies. */ -final class ISOCurrencyProvider +final class IsoCurrencyProvider implements CurrencyProvider { - private static ?ISOCurrencyProvider $instance = null; + private static ?IsoCurrencyProvider $instance = null; /** * The raw currency data, indexed by currency code. @@ -25,27 +25,27 @@ final class ISOCurrencyProvider * * This property is set on-demand, as soon as required. * - * @psalm-var array|null + * @psalm-var array */ - private ?array $numericToCurrency = null; + private array $numericToCurrency = []; /** * An associative array of country code to currency codes. * * This property is set on-demand, as soon as required. * - * @psalm-var array>|null + * @psalm-var array> */ - private ?array $countryToCurrency = null; + private array $countryToCurrency = []; /** * The Currency instances. * * The instances are created on-demand, as soon as they are requested. * - * @psalm-var array + * @psalm-var array * - * @var Currency[] + * @var IsoCurrency[] */ private array $currencies = []; @@ -67,12 +67,12 @@ private function __construct() /** * Returns the singleton instance of ISOCurrencyProvider. * - * @return ISOCurrencyProvider + * @return IsoCurrencyProvider */ - public static function getInstance() : ISOCurrencyProvider + public static function getInstance() : IsoCurrencyProvider { if (self::$instance === null) { - self::$instance = new ISOCurrencyProvider(); + self::$instance = new IsoCurrencyProvider(); } return self::$instance; @@ -81,52 +81,95 @@ public static function getInstance() : ISOCurrencyProvider /** * Returns the currency matching the given currency code. * - * @param string|int $currencyCode The 3-letter or numeric ISO 4217 currency code. + * @param string|int $code The 3-letter or numeric ISO 4217 currency code. * - * @return Currency The currency. + * @return IsoCurrency The currency. * - * @throws UnknownCurrencyException If the currency code is not known. + * @throws UnknownIsoCurrencyException If the currency code is not known. */ - public function getCurrency(string|int $currencyCode) : Currency + public function getByCode(string|int $code) : IsoCurrency { - if (is_int($currencyCode)) { - if ($this->numericToCurrency === null) { + $currency = $this->findByCode($code); + + if ($currency === null) { + if (is_int($code)) { + throw UnknownIsoCurrencyException::unknownCurrency(null, $code); + } + + throw UnknownIsoCurrencyException::unknownCurrency($code); + } + + return $currency; + } + + public function hasCode(?string $currencyCode = null, ?int $numericCode = null) : bool + { + if ($currencyCode === null && $numericCode === null) { + throw new \InvalidArgumentException('At least one of currency code or numeric code passed must be provided'); + } + + if ($numericCode !== null) { + if ($this->numericToCurrency === []) { $this->numericToCurrency = require __DIR__ . '/../data/numeric-to-currency.php'; } - if (isset($this->numericToCurrency[$currencyCode])) { - return $this->getCurrency($this->numericToCurrency[$currencyCode]); + if (isset($this->numericToCurrency[$numericCode])) { + if ($currencyCode !== null) { + return $currencyCode === $this->numericToCurrency[$numericCode] && $this->hasCode($this->numericToCurrency[$numericCode]); + } + + return $this->hasCode($this->numericToCurrency[$numericCode]); } - throw UnknownCurrencyException::unknownCurrency($currencyCode); + return false; } if (isset($this->currencies[$currencyCode])) { - return $this->currencies[$currencyCode]; + return true; } if (! isset($this->currencyData[$currencyCode])) { - throw UnknownCurrencyException::unknownCurrency($currencyCode); + return false; } - $currency = new Currency(... $this->currencyData[$currencyCode]); + return true; + } + + private function findByCode(string|int $currencyCode) : ?IsoCurrency { + + + if (is_int($currencyCode)) { + if (!$this->hasCode(null, $currencyCode)) { + return null; + } + + $currencyCode = $this->numericToCurrency[$currencyCode]; + } + + if (!$this->hasCode($currencyCode)) { + return null; + } + + if (isset($this->currencies[$currencyCode])) { + return $this->currencies[$currencyCode]; + } - return $this->currencies[$currencyCode] = $currency; + return $this->currencies[$currencyCode] = new IsoCurrency(... $this->currencyData[$currencyCode]); } /** * Returns all the available currencies. * - * @psalm-return array + * @psalm-return array * - * @return Currency[] The currencies, indexed by currency code. + * @return IsoCurrency[] The currencies, indexed by currency code. */ public function getAvailableCurrencies() : array { if ($this->isPartial) { foreach ($this->currencyData as $currencyCode => $data) { if (! isset($this->currencies[$currencyCode])) { - $this->currencies[$currencyCode] = new Currency(... $data); + $this->currencies[$currencyCode] = new IsoCurrency(... $data); } } @@ -143,11 +186,11 @@ public function getAvailableCurrencies() : array * * @param string $countryCode The 2-letter ISO 3166-1 country code. * - * @return Currency + * @return IsoCurrency * - * @throws UnknownCurrencyException If the country code is not known, or the country has no single currency. + * @throws UnknownIsoCurrencyException If the country code is not known, or the country has no single currency. */ - public function getCurrencyForCountry(string $countryCode) : Currency + public function getByCountryCode(string $countryCode) : IsoCurrency { $currencies = $this->getCurrenciesForCountry($countryCode); @@ -158,16 +201,16 @@ public function getCurrencyForCountry(string $countryCode) : Currency } if ($count === 0) { - throw UnknownCurrencyException::noCurrencyForCountry($countryCode); + throw UnknownIsoCurrencyException::noCurrencyForCountry($countryCode); } $currencyCodes = []; foreach ($currencies as $currency) { - $currencyCodes[] = $currency->getCurrencyCode(); + $currencyCodes[] = $currency->getCode(); } - throw UnknownCurrencyException::noSingleCurrencyForCountry($countryCode, $currencyCodes); + throw UnknownIsoCurrencyException::noSingleCurrencyForCountry($countryCode, $currencyCodes); } /** @@ -177,11 +220,11 @@ public function getCurrencyForCountry(string $countryCode) : Currency * * @param string $countryCode The 2-letter ISO 3166-1 country code. * - * @return Currency[] + * @return IsoCurrency[] */ public function getCurrenciesForCountry(string $countryCode) : array { - if ($this->countryToCurrency === null) { + if ($this->countryToCurrency === []) { $this->countryToCurrency = require __DIR__ . '/../data/country-to-currency.php'; } @@ -189,7 +232,7 @@ public function getCurrenciesForCountry(string $countryCode) : array if (isset($this->countryToCurrency[$countryCode])) { foreach ($this->countryToCurrency[$countryCode] as $currencyCode) { - $result[] = $this->getCurrency($currencyCode); + $result[] = $this->getByCode($currencyCode); } } diff --git a/src/Money.php b/src/Money.php index 23cc02c..a586984 100644 --- a/src/Money.php +++ b/src/Money.php @@ -6,7 +6,7 @@ use Brick\Money\Context\DefaultContext; use Brick\Money\Exception\MoneyMismatchException; -use Brick\Money\Exception\UnknownCurrencyException; +use Brick\Money\Exception\UnknownIsoCurrencyException; use Brick\Math\BigDecimal; use Brick\Math\BigInteger; @@ -16,7 +16,6 @@ use Brick\Math\Exception\MathException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Exception\RoundingNecessaryException; -use InvalidArgumentException; /** * A monetary value in a given currency. This class is immutable. @@ -31,7 +30,7 @@ * - CustomContext handles monies with a custom scale, and optionally step. * - AutoContext automatically adjusts the scale of the money to the minimum required. */ -final class Money extends AbstractMoney +class Money extends AbstractMoney { /** * The amount. @@ -170,7 +169,7 @@ public static function create(BigNumber $amount, Currency $currency, Context $co * @return Money * * @throws NumberFormatException If the amount is a string in a non-supported format. - * @throws UnknownCurrencyException If the currency is an unknown currency code. + * @throws UnknownIsoCurrencyException If the currency is an unknown currency code. * @throws RoundingNecessaryException If the rounding mode is RoundingMode::UNNECESSARY, and rounding is necessary * to represent the amount at the requested scale. */ @@ -181,7 +180,7 @@ public static function of( RoundingMode $roundingMode = RoundingMode::UNNECESSARY, ) : Money { if (! $currency instanceof Currency) { - $currency = Currency::of($currency); + $currency = static::getCurrencyProvider()->getByCode($currency); } if ($context === null) { @@ -208,7 +207,7 @@ public static function of( * @return Money * * @throws NumberFormatException If the amount is a string in a non-supported format. - * @throws UnknownCurrencyException If the currency is an unknown currency code. + * @throws UnknownIsoCurrencyException If the currency is an unknown currency code. * @throws RoundingNecessaryException If the rounding mode is RoundingMode::UNNECESSARY, and rounding is necessary * to represent the amount at the requested scale. */ @@ -219,7 +218,7 @@ public static function ofMinor( RoundingMode $roundingMode = RoundingMode::UNNECESSARY, ) : Money { if (! $currency instanceof Currency) { - $currency = Currency::of($currency); + $currency = static::getCurrencyProvider()->getByCode($currency); } if ($context === null) { @@ -245,7 +244,7 @@ public static function ofMinor( public static function zero(Currency|string|int $currency, ?Context $context = null) : Money { if (! $currency instanceof Currency) { - $currency = Currency::of($currency); + $currency = static::getCurrencyProvider()->getByCode($currency); } if ($context === null) { @@ -719,7 +718,7 @@ public function negated() : Money * * @return Money * - * @throws UnknownCurrencyException If an unknown currency code is given. + * @throws UnknownIsoCurrencyException If an unknown currency code is given. * @throws MathException If the exchange rate or rounding mode is invalid, or rounding is necessary. */ public function convertedTo( @@ -729,7 +728,7 @@ public function convertedTo( RoundingMode $roundingMode = RoundingMode::UNNECESSARY, ) : Money { if (! $currency instanceof Currency) { - $currency = Currency::of($currency); + $currency = static::getCurrencyProvider()->getByCode($currency); } if ($context === null) { @@ -755,7 +754,7 @@ public function formatWith(\NumberFormatter $formatter) : string { return $formatter->formatCurrency( $this->amount->toFloat(), - $this->currency->getCurrencyCode() + $this->currency->getCode() ); } diff --git a/src/MoneyBag.php b/src/MoneyBag.php index 910cb93..53bf6de 100644 --- a/src/MoneyBag.php +++ b/src/MoneyBag.php @@ -25,19 +25,13 @@ final class MoneyBag implements MoneyContainer /** * Returns the amount in the given currency contained in the bag, as a rational number. * - * Non-ISO (non-numeric) currency codes are accepted. - * - * @param Currency|string|int $currency The Currency instance, currency code or ISO numeric currency code. + * @param Currency|string $currency The Currency instance or currency code. * * @return BigRational */ - public function getAmount(Currency|string|int $currency) : BigRational + public function getAmount(Currency|string $currency) : BigRational { - if (is_int($currency)) { - $currencyCode = (string) Currency::of($currency); - } else { - $currencyCode = (string) $currency; - } + $currencyCode = $currency instanceof Currency ? $currency->getCode() : $currency; return $this->amounts[$currencyCode] ?? BigRational::zero(); } diff --git a/src/MoneyComparator.php b/src/MoneyComparator.php index 6fbb0f9..3420de0 100644 --- a/src/MoneyComparator.php +++ b/src/MoneyComparator.php @@ -52,8 +52,8 @@ public function __construct(ExchangeRateProvider $provider) */ public function compare(Money $a, Money $b) : int { - $aCurrencyCode = $a->getCurrency()->getCurrencyCode(); - $bCurrencyCode = $b->getCurrency()->getCurrencyCode(); + $aCurrencyCode = $a->getCurrency()->getCode(); + $bCurrencyCode = $b->getCurrency()->getCode(); if ($aCurrencyCode === $bCurrencyCode) { return $a->compareTo($b); diff --git a/src/RationalMoney.php b/src/RationalMoney.php index 5d7ef67..9770f7f 100644 --- a/src/RationalMoney.php +++ b/src/RationalMoney.php @@ -16,7 +16,7 @@ * This is used to represent intermediate calculation results, and may not be exactly convertible to a decimal amount * with a finite number of digits. The final conversion to a Money may require rounding. */ -final class RationalMoney extends AbstractMoney +class RationalMoney extends AbstractMoney { private readonly BigRational $amount; @@ -47,7 +47,7 @@ public static function of(BigNumber|int|float|string $amount, Currency|string|in $amount = BigRational::of($amount); if (! $currency instanceof Currency) { - $currency = Currency::of($currency); + $currency = static::getCurrencyProvider()->getByCode($currency); } return new RationalMoney($amount, $currency); diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index c23b508..2c7c10d 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -5,7 +5,7 @@ namespace Brick\Money\Tests; use Brick\Money\Context; -use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Money\Money; use Brick\Money\MoneyBag; use Brick\Money\RationalMoney; @@ -94,9 +94,9 @@ final protected function assertRationalMoneyEquals(string $expected, RationalMon self::assertSame($expected, (string) $actual); } - final protected function assertCurrencyEquals(string $currencyCode, int $numericCode, string $name, int $defaultFractionDigits, Currency $currency) : void + final protected function assertCurrencyEquals(string $currencyCode, int $numericCode, string $name, int $defaultFractionDigits, IsoCurrency $currency) : void { - self::assertSame($currencyCode, $currency->getCurrencyCode()); + self::assertSame($currencyCode, $currency->getCode()); self::assertSame($numericCode, $currency->getNumericCode()); self::assertSame($name, $currency->getName()); self::assertSame($defaultFractionDigits, $currency->getDefaultFractionDigits()); diff --git a/tests/Context/AutoContextTest.php b/tests/Context/AutoContextTest.php index 077f864..d919a7a 100644 --- a/tests/Context/AutoContextTest.php +++ b/tests/Context/AutoContextTest.php @@ -9,7 +9,7 @@ use Brick\Math\RoundingMode; use Brick\Money\Context\AutoContext; use Brick\Money\Context\CashContext; -use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Money\Tests\AbstractTestCase; use PHPUnit\Framework\Attributes\DataProvider; @@ -22,7 +22,7 @@ class AutoContextTest extends AbstractTestCase public function testApplyTo(string $amount, string $currency, RoundingMode $roundingMode, string $expected) : void { $amount = BigNumber::of($amount); - $currency = Currency::of($currency); + $currency = IsoCurrency::of($currency); $context = new AutoContext(); diff --git a/tests/Context/CashContextTest.php b/tests/Context/CashContextTest.php index f6a36ea..99d94bb 100644 --- a/tests/Context/CashContextTest.php +++ b/tests/Context/CashContextTest.php @@ -8,7 +8,7 @@ use Brick\Math\Exception\RoundingNecessaryException; use Brick\Math\RoundingMode; use Brick\Money\Context\CashContext; -use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Money\Tests\AbstractTestCase; use PHPUnit\Framework\Attributes\DataProvider; @@ -21,7 +21,7 @@ class CashContextTest extends AbstractTestCase public function testApplyTo(int $step, string $amount, string $currency, RoundingMode $roundingMode, string $expected) : void { $amount = BigNumber::of($amount); - $currency = Currency::of($currency); + $currency = IsoCurrency::of($currency); $context = new CashContext($step); diff --git a/tests/Context/CustomContextTest.php b/tests/Context/CustomContextTest.php index da0b5e6..3221f8f 100644 --- a/tests/Context/CustomContextTest.php +++ b/tests/Context/CustomContextTest.php @@ -8,7 +8,7 @@ use Brick\Math\Exception\RoundingNecessaryException; use Brick\Math\RoundingMode; use Brick\Money\Context\CustomContext; -use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Money\Tests\AbstractTestCase; use PHPUnit\Framework\Attributes\DataProvider; @@ -21,7 +21,7 @@ class CustomContextTest extends AbstractTestCase public function testApplyTo(int $scale, int $step, string $amount, string $currency, RoundingMode $roundingMode, string $expected) : void { $amount = BigNumber::of($amount); - $currency = Currency::of($currency); + $currency = IsoCurrency::of($currency); $context = new CustomContext($scale, $step); diff --git a/tests/Context/DefaultContextTest.php b/tests/Context/DefaultContextTest.php index 8a0f00e..7d94531 100644 --- a/tests/Context/DefaultContextTest.php +++ b/tests/Context/DefaultContextTest.php @@ -8,7 +8,7 @@ use Brick\Math\Exception\RoundingNecessaryException; use Brick\Math\RoundingMode; use Brick\Money\Context\DefaultContext; -use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Money\Tests\AbstractTestCase; use PHPUnit\Framework\Attributes\DataProvider; @@ -21,7 +21,7 @@ class DefaultContextTest extends AbstractTestCase public function testApplyTo(string $amount, string $currency, RoundingMode $roundingMode, string $expected) : void { $amount = BigNumber::of($amount); - $currency = Currency::of($currency); + $currency = IsoCurrency::of($currency); $context = new DefaultContext(); diff --git a/tests/CurrencyConverterTest.php b/tests/CurrencyConverterTest.php index 00758d3..2f0b03b 100644 --- a/tests/CurrencyConverterTest.php +++ b/tests/CurrencyConverterTest.php @@ -10,9 +10,11 @@ use Brick\Money\Context\AutoContext; use Brick\Money\Context\CustomContext; use Brick\Money\Context\DefaultContext; +use Brick\Money\Currency; use Brick\Money\CurrencyConverter; use Brick\Money\Exception\CurrencyConversionException; use Brick\Money\ExchangeRateProvider\ConfigurableProvider; +use Brick\Money\IsoCurrency; use Brick\Money\Money; use Brick\Money\MoneyBag; use Brick\Money\RationalMoney; @@ -35,12 +37,12 @@ private function createCurrencyConverter() : CurrencyConverter /** * @param array $money The base money. - * @param string $toCurrency The currency code to convert to. + * @param Currency $toCurrency The currency code to convert to. * @param RoundingMode $roundingMode The rounding mode to use. * @param string $expectedResult The expected money's string representation, or an exception class name. */ #[DataProvider('providerConvertMoney')] - public function testConvertMoney(array $money, string $toCurrency, RoundingMode $roundingMode, string $expectedResult) : void + public function testConvertMoney(array $money, Currency $toCurrency, RoundingMode $roundingMode, string $expectedResult) : void { $money = Money::of(...$money); $currencyConverter = $this->createCurrencyConverter(); @@ -59,30 +61,30 @@ public function testConvertMoney(array $money, string $toCurrency, RoundingMode public static function providerConvertMoney() : array { return [ - [['1.23', 'EUR'], 'USD', RoundingMode::DOWN, 'USD 1.35'], - [['1.23', 'EUR'], 'USD', RoundingMode::UP, 'USD 1.36'], - [['1.10', 'EUR'], 'USD', RoundingMode::DOWN, 'USD 1.21'], - [['1.10', 'EUR'], 'USD', RoundingMode::UP, 'USD 1.21'], - [['123.57', 'USD'], 'EUR', RoundingMode::DOWN, 'EUR 112.33'], - [['123.57', 'USD'], 'EUR', RoundingMode::UP, 'EUR 112.34'], - [['123.57', 'USD'], 'EUR', RoundingMode::UNNECESSARY, RoundingNecessaryException::class], - [['1724657496.87', 'USD', new AutoContext()], 'EUR', RoundingMode::UNNECESSARY, 'EUR 1567870451.70'], - [['127.367429', 'BSD', new AutoContext()], 'USD', RoundingMode::UP, 'USD 127.37'], - [['1.23', 'USD'], 'BSD', RoundingMode::DOWN, CurrencyConversionException::class], - [['1.23', 'EUR'], 'EUR', RoundingMode::UNNECESSARY, 'EUR 1.23'], - [['123456.789', 'JPY', new AutoContext()], 'JPY', RoundingMode::HALF_EVEN, 'JPY 123457'], + [['1.23', 'EUR'], IsoCurrency::of('USD'), RoundingMode::DOWN, 'USD 1.35'], + [['1.23', 'EUR'], IsoCurrency::of('USD'), RoundingMode::UP, 'USD 1.36'], + [['1.10', 'EUR'], IsoCurrency::of('USD'), RoundingMode::DOWN, 'USD 1.21'], + [['1.10', 'EUR'], IsoCurrency::of('USD'), RoundingMode::UP, 'USD 1.21'], + [['123.57', 'USD'], IsoCurrency::of('EUR'), RoundingMode::DOWN, 'EUR 112.33'], + [['123.57', 'USD'], IsoCurrency::of('EUR'), RoundingMode::UP, 'EUR 112.34'], + [['123.57', 'USD'], IsoCurrency::of('EUR'), RoundingMode::UNNECESSARY, RoundingNecessaryException::class], + [['1724657496.87', 'USD', new AutoContext()], IsoCurrency::of('EUR'), RoundingMode::UNNECESSARY, 'EUR 1567870451.70'], + [['127.367429', 'BSD', new AutoContext()], IsoCurrency::of('USD'), RoundingMode::UP, 'USD 127.37'], + [['1.23', 'USD'], IsoCurrency::of('BSD'), RoundingMode::DOWN, CurrencyConversionException::class], + [['1.23', 'EUR'], IsoCurrency::of('EUR'), RoundingMode::UNNECESSARY, 'EUR 1.23'], + [['123456.789', 'JPY', new AutoContext()], IsoCurrency::of('JPY'), RoundingMode::HALF_EVEN, 'JPY 123457'], ]; } /** * @param array $monies The mixed currency monies to add. - * @param string $currency The target currency code. + * @param Currency $currency The target currency code. * @param Context $context The target context. * @param RoundingMode $roundingMode The rounding mode to use. * @param string $total The expected total. */ #[DataProvider('providerConvertMoneyBag')] - public function testConvertMoneyBag(array $monies, string $currency, Context $context, RoundingMode $roundingMode, string $total) : void + public function testConvertMoneyBag(array $monies, Currency $currency, Context $context, RoundingMode $roundingMode, string $total) : void { $exchangeRateProvider = new ConfigurableProvider(); $exchangeRateProvider->setExchangeRate('EUR', 'USD', '1.23456789'); @@ -102,21 +104,21 @@ public function testConvertMoneyBag(array $monies, string $currency, Context $co public static function providerConvertMoneyBag() : array { return [ - [[['354.40005', 'EUR'], ['3.1234', 'JPY']], 'USD', new DefaultContext(), RoundingMode::DOWN, 'USD 437.56'], - [[['354.40005', 'EUR'], ['3.1234', 'JPY']], 'USD', new DefaultContext(), RoundingMode::UP, 'USD 437.57'], + [[['354.40005', 'EUR'], ['3.1234', 'JPY']], IsoCurrency::of('USD'), new DefaultContext(), RoundingMode::DOWN, 'USD 437.56'], + [[['354.40005', 'EUR'], ['3.1234', 'JPY']], IsoCurrency::of('USD'), new DefaultContext(), RoundingMode::UP, 'USD 437.57'], - [[['1234.56', 'EUR'], ['31562', 'JPY']], 'USD', new CustomContext(6), RoundingMode::DOWN, 'USD 1835.871591'], - [[['1234.56', 'EUR'], ['31562', 'JPY']], 'USD', new CustomContext(6), RoundingMode::UP, 'USD 1835.871592'] + [[['1234.56', 'EUR'], ['31562', 'JPY']], IsoCurrency::of('USD'), new CustomContext(6), RoundingMode::DOWN, 'USD 1835.871591'], + [[['1234.56', 'EUR'], ['31562', 'JPY']], IsoCurrency::of('USD'), new CustomContext(6), RoundingMode::UP, 'USD 1835.871592'] ]; } /** * @param array $monies The mixed monies to add. - * @param string $currency The target currency code. + * @param Currency $currency The target currency code. * @param string $expectedTotal The expected total. */ #[DataProvider('providerConvertMoneyBagToRational')] - public function testConvertMoneyBagToRational(array $monies, string $currency, string $expectedTotal) : void + public function testConvertMoneyBagToRational(array $monies, Currency $currency, string $expectedTotal) : void { $exchangeRateProvider = new ConfigurableProvider(); $exchangeRateProvider->setExchangeRate('EUR', 'USD', '1.123456789'); @@ -138,19 +140,19 @@ public function testConvertMoneyBagToRational(array $monies, string $currency, s public static function providerConvertMoneyBagToRational() : array { return [ - [[['354.40005', 'EUR'], ['3.1234', 'JPY']], 'USD', 'USD 19909199529475444524673813/50000000000000000000000'], - [[['1234.56', 'EUR'], ['31562', 'JPY']], 'USD', 'USD 8493491351479471587209/5000000000000000000'] + [[['354.40005', 'EUR'], ['3.1234', 'JPY']], IsoCurrency::of('USD'), 'USD 19909199529475444524673813/50000000000000000000000'], + [[['1234.56', 'EUR'], ['31562', 'JPY']], IsoCurrency::of('USD'), 'USD 8493491351479471587209/5000000000000000000'] ]; } /** * @param array $money The original amount and currency. - * @param string $toCurrency The currency code to convert to. + * @param Currency $toCurrency The currency code to convert to. * @param RoundingMode $roundingMode The rounding mode to use. * @param string $expectedResult The expected money's string representation, or an exception class name. */ #[DataProvider('providerConvertRationalMoney')] - public function testConvertRationalMoney(array $money, string $toCurrency, RoundingMode $roundingMode, string $expectedResult) : void + public function testConvertRationalMoney(array $money, Currency $toCurrency, RoundingMode $roundingMode, string $expectedResult) : void { $currencyConverter = $this->createCurrencyConverter(); @@ -170,10 +172,10 @@ public function testConvertRationalMoney(array $money, string $toCurrency, Round public static function providerConvertRationalMoney() : array { return [ - [['7/9', 'USD'], 'EUR', RoundingMode::DOWN, 'EUR 0.70'], - [['7/9', 'USD'], 'EUR', RoundingMode::UP, 'EUR 0.71'], - [['4/3', 'EUR'], 'USD', RoundingMode::DOWN, 'USD 1.46'], - [['4/3', 'EUR'], 'USD', RoundingMode::UP, 'USD 1.47'], + [['7/9', 'USD'], IsoCurrency::of('EUR'), RoundingMode::DOWN, 'EUR 0.70'], + [['7/9', 'USD'], IsoCurrency::of('EUR'), RoundingMode::UP, 'EUR 0.71'], + [['4/3', 'EUR'], IsoCurrency::of('USD'), RoundingMode::DOWN, 'USD 1.46'], + [['4/3', 'EUR'], IsoCurrency::of('USD'), RoundingMode::UP, 'USD 1.47'], ]; } } diff --git a/tests/CustomCurrency.php b/tests/CustomCurrency.php new file mode 100644 index 0000000..5f79df0 --- /dev/null +++ b/tests/CustomCurrency.php @@ -0,0 +1,33 @@ +getCode(); + } + + public function getCode(): string + { + return 'CUSTOM'; + } + + public function getDefaultFractionDigits(): int + { + return 3; + } + + public function jsonSerialize(): mixed + { + return $this->getCode(); + } + + public function is(Currency $currency): bool + { + return $currency->getCode() === $this->getCode(); + } +} \ No newline at end of file diff --git a/tests/ISOCurrencyProviderTest.php b/tests/ISOCurrencyProviderTest.php index 05890ef..e75e4f4 100644 --- a/tests/ISOCurrencyProviderTest.php +++ b/tests/ISOCurrencyProviderTest.php @@ -4,9 +4,9 @@ namespace Brick\Money\Tests; -use Brick\Money\Currency; -use Brick\Money\Exception\UnknownCurrencyException; -use Brick\Money\ISOCurrencyProvider; +use Brick\Money\IsoCurrency; +use Brick\Money\Exception\UnknownIsoCurrencyException; +use Brick\Money\IsoCurrencyProvider; use PHPUnit\Framework\Attributes\DataProvider; /** @@ -23,7 +23,7 @@ class ISOCurrencyProviderTest extends AbstractTestCase */ public static function setUpBeforeClass() : void { - $reflection = new \ReflectionProperty(ISOCurrencyProvider::class, 'instance'); + $reflection = new \ReflectionProperty(IsoCurrencyProvider::class, 'instance'); $reflection->setAccessible(true); $reflection->setValue(null); } @@ -31,12 +31,12 @@ public static function setUpBeforeClass() : void #[DataProvider('providerGetCurrency')] public function testGetCurrency(string $currencyCode, int $numericCode, string $name, int $defaultFractionDigits) : void { - $provider = ISOCurrencyProvider::getInstance(); + $provider = IsoCurrencyProvider::getInstance(); - $currency = $provider->getCurrency($currencyCode); + $currency = $provider->getByCode($currencyCode); $this->assertCurrencyEquals($currencyCode, $numericCode, $name, $defaultFractionDigits, $currency); - $currency = $provider->getCurrency($numericCode); + $currency = $provider->getByCode($numericCode); $this->assertCurrencyEquals($currencyCode, $numericCode, $name, $defaultFractionDigits, $currency); } @@ -59,8 +59,8 @@ public static function providerGetCurrency() : array #[DataProvider('providerUnknownCurrency')] public function testGetUnknownCurrency(string|int $currencyCode) : void { - $this->expectException(UnknownCurrencyException::class); - ISOCurrencyProvider::getInstance()->getCurrency($currencyCode); + $this->expectException(UnknownIsoCurrencyException::class); + IsoCurrencyProvider::getInstance()->getByCode($currencyCode); } public static function providerUnknownCurrency() : array @@ -73,18 +73,18 @@ public static function providerUnknownCurrency() : array public function testGetAvailableCurrencies() : void { - $provider = ISOCurrencyProvider::getInstance(); + $provider = IsoCurrencyProvider::getInstance(); - $eur = $provider->getCurrency('EUR'); - $gbp = $provider->getCurrency('GBP'); - $usd = $provider->getCurrency('USD'); + $eur = $provider->getByCode('EUR'); + $gbp = $provider->getByCode('GBP'); + $usd = $provider->getByCode('USD'); $availableCurrencies = $provider->getAvailableCurrencies(); self::assertGreaterThan(100, count($availableCurrencies)); foreach ($availableCurrencies as $currency) { - self::assertInstanceOf(Currency::class, $currency); + self::assertInstanceOf(IsoCurrency::class, $currency); } self::assertSame($eur, $availableCurrencies['EUR']); diff --git a/tests/CurrencyTest.php b/tests/IsoCurrencyTest.php similarity index 66% rename from tests/CurrencyTest.php rename to tests/IsoCurrencyTest.php index c040dff..4f183c9 100644 --- a/tests/CurrencyTest.php +++ b/tests/IsoCurrencyTest.php @@ -4,14 +4,14 @@ namespace Brick\Money\Tests; -use Brick\Money\Currency; -use Brick\Money\Exception\UnknownCurrencyException; +use Brick\Money\IsoCurrency; +use Brick\Money\Exception\UnknownIsoCurrencyException; use PHPUnit\Framework\Attributes\DataProvider; /** * Unit tests for class Currency. */ -class CurrencyTest extends AbstractTestCase +class IsoCurrencyTest extends AbstractTestCase { /** * @param string $currencyCode The currency code. @@ -22,10 +22,10 @@ class CurrencyTest extends AbstractTestCase #[DataProvider('providerOf')] public function testOf(string $currencyCode, int $numericCode, int $fractionDigits, string $name) : void { - $currency = Currency::of($currencyCode); + $currency = IsoCurrency::of($currencyCode); $this->assertCurrencyEquals($currencyCode, $numericCode, $name, $fractionDigits, $currency); - $currency = Currency::of($numericCode); + $currency = IsoCurrency::of($numericCode); $this->assertCurrencyEquals($currencyCode, $numericCode, $name, $fractionDigits, $currency); } @@ -43,8 +43,8 @@ public static function providerOf() : array #[DataProvider('providerOfUnknownCurrencyCode')] public function testOfUnknownCurrencyCode(string|int $currencyCode) : void { - $this->expectException(UnknownCurrencyException::class); - Currency::of($currencyCode); + $this->expectException(UnknownIsoCurrencyException::class); + IsoCurrency::of($currencyCode); } public static function providerOfUnknownCurrencyCode() : array @@ -57,13 +57,13 @@ public static function providerOfUnknownCurrencyCode() : array public function testConstructor() : void { - $bitCoin = new Currency('BTC', -1, 'BitCoin', 8); - $this->assertCurrencyEquals('BTC', -1, 'BitCoin', 8, $bitCoin); + $euro = new IsoCurrency('EUR', 978, 'Euro', 8); + $this->assertCurrencyEquals('EUR', 978, 'Euro', 8, $euro); } public function testOfReturnsSameInstance() : void { - self::assertSame(Currency::of('EUR'), Currency::of('EUR')); + self::assertSame(IsoCurrency::of('EUR'), IsoCurrency::of('EUR')); } #[DataProvider('providerOfCountry')] @@ -73,11 +73,11 @@ public function testOfCountry(string $countryCode, string $expected) : void $this->expectException($expected); } - $actual = Currency::ofCountry($countryCode); + $actual = IsoCurrency::ofCountry($countryCode); if (! $this->isExceptionClass($expected)) { - self::assertInstanceOf(Currency::class, $actual); - self::assertSame($expected, $actual->getCurrencyCode()); + self::assertInstanceOf(IsoCurrency::class, $actual); + self::assertSame($expected, $actual->getCode()); } } @@ -92,27 +92,27 @@ public static function providerOfCountry() : array ['GB', 'GBP'], ['IT', 'EUR'], ['US', 'USD'], - ['AQ', UnknownCurrencyException::class], // no currency - ['CU', UnknownCurrencyException::class], // 2 currencies - ['XX', UnknownCurrencyException::class], // unknown + ['AQ', UnknownIsoCurrencyException::class], // no currency + ['CU', UnknownIsoCurrencyException::class], // 2 currencies + ['XX', UnknownIsoCurrencyException::class], // unknown ]; } public function testCreateWithNegativeFractionDigits() : void { $this->expectException(\InvalidArgumentException::class); - new Currency('BTC', 0, 'BitCoin', -1); + new IsoCurrency('BTC', 0, 'BitCoin', -1); } public function testIs() : void { - $currency = Currency::of('EUR'); + $currency = IsoCurrency::of('EUR'); - self::assertTrue($currency->is('EUR')); - self::assertTrue($currency->is(978)); + self::assertTrue($currency->is(IsoCurrency::of('EUR'))); + self::assertTrue($currency->is(IsoCurrency::of(978))); - self::assertFalse($currency->is('USD')); - self::assertFalse($currency->is(840)); + self::assertFalse($currency->is(IsoCurrency::of('USD'))); + self::assertFalse($currency->is(IsoCurrency::of(840))); $clone = clone $currency; @@ -121,7 +121,7 @@ public function testIs() : void } #[DataProvider('providerJsonSerialize')] - public function testJsonSerialize(Currency $currency, string $expected): void + public function testJsonSerialize(IsoCurrency $currency, string $expected): void { self::assertSame($expected, $currency->jsonSerialize()); self::assertSame(json_encode($expected), json_encode($currency)); @@ -130,7 +130,7 @@ public function testJsonSerialize(Currency $currency, string $expected): void public static function providerJsonSerialize(): array { return [ - [Currency::of('USD'), 'USD'] + [IsoCurrency::of('USD'), 'USD'] ]; } } diff --git a/tests/MoneyBagTest.php b/tests/MoneyBagTest.php index 815a7ae..954d739 100644 --- a/tests/MoneyBagTest.php +++ b/tests/MoneyBagTest.php @@ -5,7 +5,7 @@ namespace Brick\Money\Tests; use Brick\Money\Context\AutoContext; -use Brick\Money\Currency; +use Brick\Money\IsoCurrencyProvider; use Brick\Money\Money; use Brick\Money\MoneyBag; use Brick\Money\RationalMoney; @@ -23,7 +23,7 @@ public function testEmptyMoneyBag() : void $this->assertMoneyBagContains([], $moneyBag); foreach (['USD', 'EUR', 'GBP', 'JPY'] as $currencyCode) { - self::assertTrue($moneyBag->getAmount($currencyCode)->isZero()); + self::assertTrue($moneyBag->getAmount(IsoCurrencyProvider::getInstance()->getByCode($currencyCode))->isZero()); } } @@ -55,7 +55,7 @@ public function testAddSubtractMoney() : MoneyBag #[Depends('testAddSubtractMoney')] public function testAddCustomCurrency(MoneyBag $moneyBag) : void { - $moneyBag->add(Money::of('0.1234', new Currency('BTC', 0, 'Bitcoin', 8))); - $this->assertMoneyBagContains(['EUR' => '21284003/60000', 'JPY' => '4.1234', 'BTC' => '0.1234'], $moneyBag); + $moneyBag->add(Money::of('0.123', new CustomCurrency())); + $this->assertMoneyBagContains(['EUR' => '21284003/60000', 'JPY' => '4.1234', 'CUSTOM' => '0.123'], $moneyBag); } } diff --git a/tests/MoneyTest.php b/tests/MoneyTest.php index fef317c..4d3ab8c 100644 --- a/tests/MoneyTest.php +++ b/tests/MoneyTest.php @@ -16,7 +16,6 @@ use Brick\Money\Context\CashContext; use Brick\Money\Context\CustomContext; use Brick\Money\Context\DefaultContext; -use Brick\Money\Currency; use Brick\Money\Exception\MoneyMismatchException; use Brick\Money\Money; use PHPUnit\Framework\Attributes\DataProvider; @@ -55,8 +54,8 @@ public static function providerOf() : array ['EUR 9.00', 9, 978], ['EUR 0.42', BigRational::of('3/7'), 'EUR', null, RoundingMode::DOWN], ['EUR 0.43', BigRational::of('3/7'), 'EUR', null, RoundingMode::UP], - ['CUSTOM 0.428', BigRational::of('3/7'), new Currency('CUSTOM', 0, '', 3), null, RoundingMode::DOWN], - ['CUSTOM 0.4286', BigRational::of('3/7'), new Currency('CUSTOM', 0, '', 3), new CustomContext(4, 1), RoundingMode::UP], + ['CUSTOM 0.428', BigRational::of('3/7'), new CustomCurrency(), null, RoundingMode::DOWN], + ['CUSTOM 0.4286', BigRational::of('3/7'), new CustomCurrency(), new CustomContext(4, 1), RoundingMode::UP], [RoundingNecessaryException::class, '1.2', 'JPY'], [NumberFormatException::class, '1..', 'JPY'], ]; diff --git a/tests/RationalMoneyTest.php b/tests/RationalMoneyTest.php index ffb18fd..6c86e8c 100644 --- a/tests/RationalMoneyTest.php +++ b/tests/RationalMoneyTest.php @@ -12,7 +12,7 @@ use Brick\Money\Context\CashContext; use Brick\Money\Context\CustomContext; use Brick\Money\Context\DefaultContext; -use Brick\Money\Currency; +use Brick\Money\IsoCurrency; use Brick\Money\Exception\MoneyMismatchException; use Brick\Money\Money; use Brick\Money\RationalMoney; @@ -26,7 +26,7 @@ class RationalMoneyTest extends AbstractTestCase public function testGetters() : void { $amount = BigRational::of('123/456'); - $currency = Currency::of('EUR'); + $currency = IsoCurrency::of('EUR'); $money = new RationalMoney($amount, $currency);