Пакет реализован для упрощения работы с денежными значениями.
Предоставляет возможность:
- сложения, вычетания, умножения денежных значений;
- равномерного распределения денежного значения на N частей;
- пропорционального распределения денежного значения в соответствии с заданным соотношением;
- конвертации валют;
- сравнения деннежных значений между собой (с возможностью указать допустимый процент отклонения)
- выполнения всех выше перечисленных операций для денежных значений в разных валютах;
composer require v.chetkov/money
example.config.php
<?php
use Chetkov\Money\Exchanger\ExchangerInterface;
use Chetkov\Money\Exchanger\RatesProvider\SimpleExchangeRatesProvider;
use Chetkov\Money\Exchanger\SimpleExchanger;
$exchangeRates = [
'USD-RUB' => [66.34, 68.12], // Курсы покупки/продажи отличаются
'EUR-RUB' => [72.42], // Единый курс
'JPY-RUB' => [0.61], // ...
];
return [
'use_currency_conversation' => true,
'exchanger_factory' => static function () use ($exchangeRates): ExchangerInterface {
//Фабрика класса обменника
static $instance;
if (null === $instance) {
$ratesProvider = SimpleExchangeRatesProvider::getInstance($exchangeRates);
$instance = new SimpleExchanger($ratesProvider);
}
return $instance;
},
];
Если параметер 'use_currency_conversation' => false
, то при выполнении операций с экземплярами Money в разных валютах будет выброшено исключение OperationWithDifferentCurrenciesException.
В противном случае работа будет передана обменнику (о них чуть-чуть позже) для приведения второго операнда к валюте первого.
Хранением и предоставлением обменнику курсов валют занимаются классы поставщики, реализующие ExchangeRatesProviderInterface.
Далее необходимо загрузить описанный выше конфиг в PackageConfig, это необходимо для понимания:
- включена-ли автоматическая конвертация валют
- какой обменник за это отвечает
- какой поставщик предоставляет ему данные
<?php
use Chetkov\Money\LibConfig;
$config = require __DIR__ . 'config/example.config.php';
LibConfig::getInstance($config);
после чего можем выполнять различные операции:
<?php
use Chetkov\Money\Money;
$usd = Money::USD(100);
$rub = Money::RUB(200);
echo $usd->exchange(CurrencyEnum::RUB);
// Result: {"amount":6634,"currency":"RUB"}
$additionResult = $usd->add($rub);
echo $additionResult;
// Result: {"amount":103.01,"currency":"USD"}
$subtractionResult = $rub->subtract($usd);
echo $subtractionResult;
// Result: {"amount":-6434,"currency":"RUB"}
$multiplicationResult = $rub->multiple(5);
echo $multiplicationResult;
// Result: {"amount":1000,"currency":"RUB"}
$evenlyAllocationResult = $usd->allocateEvenly(4);
echo json_encode($evenlyAllocationResult);
// Result:
// [
// {"amount":25,"currency":"USD"},
// {"amount":25,"currency":"USD"},
// {"amount":25,"currency":"USD"},
// {"amount":25,"currency":"USD"}
// ]
Вы можете передать точность округления (опционально):
$evenlyAllocationResult = $usd->allocateEvenly(3, 4);
echo json_encode($evenlyAllocationResult);
// Result:
// [
// {"amount":33.3333,"currency":"USD"},
// {"amount":33.3333,"currency":"USD"},
// {"amount":33.3334,"currency":"USD"}
// ]
$proportionallyAllocationResult = $usd->allocateProportionally([0.18, 0.32, 0.5, 0.3, 1]);
echo json_encode($proportionallyAllocationResult);
// Result:
// [
// {"amount":18,"currency":"USD"},
// {"amount":32,"currency":"USD"},
// {"amount":50,"currency":"USD"},
// {"amount":30,"currency":"USD"},
// {"amount":100,"currency":"USD"}
// ]
$rub->lessThan($usd); // true
$usd->moreThan($rub); // true
$usd->equals($rub); // false
Или кросс-валютная проверка на равенство/относительное равенство.
- $isCrossCurrenciesComparison - флаг кросс-валютного сравнения (bool)
- $allowableDeviationPercent - допустимый процент отклонения (float: 0.0 .. 100.0)
$rub = Money::RUB(200);
$usd = Money::USD(3.015);
$isCrossCurrenciesComparison = true;
$rub->equals($usd, $isCrossCurrenciesComparison); // false
$allowableDeviationPercent = 0.5;
$rub->equals($usd, $isCrossCurrenciesComparison, $allowableDeviationPercent); // true
Вы можете использовать существующие классы обменников:
1 - SimpleExchanger
- получает курсы валют от поставщика;
- осуществляет поиск нужной валютной пары (в случае отсутствия в списке бросает исключение ExchangeRateWasNotFoundException);
- выполняет обмен и возвращает новый экземпляр Money;
2 - GraphRatesSearchingExchangerDecorator
- декорирует любой другой класс обменника;
- сперва делегирует работу декорируемому объекту и ловит исключение ExchangeRateWasNotFoundException;
- если тот справился с задачей самостоятельно, возвращает полученное значение;
- в противном случае строит граф из имеющихся валютных пар и пытается найти путь обмена через другие валюты, по сути 2-ой, 3-ой ... n-ый обмен (если безуспешно, бросает исключение ExchangeRateWasNotFoundException);
Или воплотить в жизнь собственные реализации.
1 - SimpleExchangeRatesProvider
- реализует шаблон Singleton;
- принимает массив с курсами валют;
- предоставляет метод установки курсов для новых валютных пар;
Самый примитивный пример: Инстанциировать и выполнить загрузку курсов валют в bootstrap файле Вашего приложения.
Вы можете единожды загрузить курсы из вашей БД. Можете сделать механизм получения курсов со стороннего ресурса (допустим с валютной биржи). Можете обновлять данные с заданым интервалом в ваших воркерах. Как всегда это зависит от ситуации, решение за Вами ;)
2 - CbrExchangeRatesProvider
- ходит в API ЦБ за курсами на указанную дату;
3 - ExchangeRatesProviderCacheDecorator
- декорирует любой другой класс постващика;
- может работать с разными стратегиями кэширования (стратегия должна реализовывать Psr\SimpleCache\CacheInterface)
- следит за TTL и регулирует процесс актуализации списка;
<?php
use Chetkov\Money\Exchanger\RatesProvider\CacheDecorator\ExchangeRatesProviderCacheDecorator;
use Chetkov\Money\Exchanger\RatesProvider\CacheDecorator\Strategy\ClassPropertyCacheStrategy;
use Chetkov\Money\Exchanger\RatesProvider\CbrExchangeRatesProvider;
$ratesProvider = new CbrExchangeRatesProvider();
$cacheStrategy = new ClassPropertyCacheStrategy();
$cachingDecorator = new ExchangeRatesProviderCacheDecorator($ratesProvider, $cacheStrategy, 60);
$cachingDecorator->getRates(); // Получает, кэширует и возвращает ставки от оригинального поставщика
sleep(55);
$cachingDecorator->getRates(); // Возвращает ставки из кэша
sleep(10);
$cachingDecorator->getRates(); // Получает, кэширует и возвращает ставки от оригинального поставщика
или так:
<?php
use Chetkov\Money\Exchanger\RatesProvider\CacheDecorator\ExchangeRatesProviderCacheDecorator;
use Chetkov\Money\Exchanger\RatesProvider\CacheDecorator\Strategy\ClassPropertyCacheStrategy;
use Chetkov\Money\Exchanger\RatesProvider\CbrExchangeRatesProvider;
$ratesProvider = new CbrExchangeRatesProvider();
$classPropertyCacheStrategy = new ClassPropertyCacheStrategy();
$redisCacheStrategy = new RedisCache();
$redisCacheDecorator = new ExchangeRatesProviderCacheDecorator($ratesProvider, $redisCacheStrategy, 3600);
$classPropertyCacheDecorator = new ExchangeRatesProviderCacheDecorator($redisCacheDecorator, $classPropertyCacheStrategy, 60);
// 1) Смотрим в кэширующем свойстве класса
// 2) Если пусто, смотрим в редис
// 3) Если и там пусто, идем к оригинальному провайдеру
Аналогично обменникам Вы можете делать собственные реализации поставщиков.
Пока на этом всё, но я думаю в скором времени пакет увидит еще множество доработок. По мере развития буду стараться поддерживать README в актуальном состоянии.
Идея была взята из книги Мартина Фаулера: "Шаблоны корпоративных приложений".