cloud-castle / currency-converter
Integer currency conversion for PHP 8.1: ISO 4217, rational exchange rates, ECB/CBR/national bank and API feeds, signature checks, no floating point.
Package info
gitverse.ru/cloud-castle/currency-converter
pkg:composer/cloud-castle/currency-converter
Requires
- php: >=8.1
Requires (Dev)
- deptrac/deptrac: ^3.0 || ^4.0
- ergebnis/composer-normalize: ^2.45
- friendsofphp/php-cs-fixer: ^3.75
- icanhazstring/composer-unused: ^0.9
- infection/infection: ^0.29 || ^0.33
- php-parallel-lint/php-parallel-lint: ^1.4
- phpmd/phpmd: ^2.15
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^1.12 || ^2.1
- phpstan/phpstan-deprecation-rules: ^1.2 || ^2.0
- phpstan/phpstan-phpunit: ^1.4 || ^2.0
- phpstan/phpstan-strict-rules: ^1.6 || ^2.0
- phpunit/phpunit: ^10.5 || ^11.5
- psalm/plugin-phpunit: ^0.19 || ^0.20
- rector/rector: ^1.2 || ^2.0
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^3.12 || ^4.0
- vimeo/psalm: ^6.0
- webmozart/assert: ^1.11
Suggests
- cloud-castle/clock: Опциональные часы для TTL-провайдера: пакету достаточно замыкания now(): int, Clock подключается вызывающим кодом
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-06 07:41:38 UTC
README
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano
CloudCastle Currency Converter
Конвертация валют для PHP 8.1 без чисел с плавающей точкой: суммы хранятся в минорных единицах, курсы — несократимыми дробями, округление задаётся явно. Справочник ISO 4217, провайдеры курсов, разбор фидов ЕЦБ, Банка России, нацбанков и коммерческих API, проверка подписи курса. Зависимостей, кроме PHP, нет.
Зачем
0.1 + 0.2 во float не равно 0.3. Для конвертации денег это значит, что
сумма может разойтись на копейку, а в реестре на тысячи строк — на рубли.
Здесь курс 1.0850 хранится как дробь 217/200, сумма 100.00 EUR — как
10000 центов, а результат считается в целых и округляется одним из семи
режимов, который вы выбрали явно.
Установка
composer require cloud-castle/currency-converter
Требуется PHP 8.1 или новее. Расширения не нужны.
Быстрый старт
use CloudCastle\CurrencyConverter\Converter;
use CloudCastle\CurrencyConverter\CurrencyCatalog;
use CloudCastle\CurrencyConverter\CurrencyPair;
use CloudCastle\CurrencyConverter\DecimalRate;
use CloudCastle\CurrencyConverter\IntegerMath;
use CloudCastle\CurrencyConverter\MoneyAllocator;
use CloudCastle\CurrencyConverter\MoneyFormatter;
use CloudCastle\CurrencyConverter\MoneyParser;
use CloudCastle\CurrencyConverter\Parser\EcbRateParser;
use CloudCastle\CurrencyConverter\Provider\ResolvingRateProvider;
use CloudCastle\CurrencyConverter\RoundingMode;
use CloudCastle\CurrencyConverter\Security\SourceGuard;
$math = new IntegerMath();
$catalog = new CurrencyCatalog();
$eur = $catalog->get('EUR');
$usd = $catalog->get('USD');
$gbp = $catalog->get('GBP');
// Курс из строки сразу становится несократимой дробью.
$rate = (new DecimalRate($math))->parse($eur, $usd, '1.0850'); // 217/200
// 100.00 EUR → USD с банковским округлением.
$money = (new MoneyParser($math))->parse('100.00', $eur);
$dollars = (new Converter($math))->convert($money, $usd, $rate, RoundingMode::HalfEven);
echo (new MoneyFormatter($math))->format($dollars); // $108.50
// Разложение без потери цента.
$parts = (new MoneyAllocator($math))->allocate($money, [1, 1, 1], RoundingMode::HalfUp);
// 3334, 3333, 3333
// Фид ЕЦБ даёт курсы к EUR; кросс GBP → USD считается через EUR.
$feed = '<Cube currency="USD" rate="1.10"/><Cube currency="GBP" rate="0.80"/>';
$table = (new EcbRateParser($catalog, new DecimalRate($math), new SourceGuard()))->parse($feed);
$cross = (new ResolvingRateProvider($table, $eur, $math))->get(new CurrencyPair($gbp, $usd, null));
$pounds = (new MoneyParser($math))->parse('10.00', $gbp);
echo (new MoneyFormatter($math))->format(
(new Converter($math))->convert($pounds, $usd, $cross, RoundingMode::HalfUp),
); // $13.75
Выводы в комментариях получены запуском этого кода.
Возможности
- Справочник ISO 4217: 174 действующие валюты и 26 выведенных из оборота; код, номер, число минорных единиц, шаг наличных, символ, имя.
- Сумма
Moneyв минорных единицах: сложение, вычитание, умножение, деление с округлением, модуль, сравнение. Переполнениеintпроверяется до операции и даёт исключение, а не тихий переход во float. - Свои валюты и криптовалюты:
CurrencyCatalog::load(), экспонента до 18, готовый списокdata/crypto.csv(BTC, ETH, SOL и другие). - Сводные операции
MoneyAggregate: минимум, максимум, сумма, среднее, остаток и отношение двух сумм несократимой дробью. - Разложение
MoneyAllocator: сумма делится по долям без потери единицы. - Мультивалютная корзина
MoneyBag: суммы копятся по валютам, итог — в одной. - Формат и разбор:
$10.50,10.50 USD, группировка$1 234 567.89; правила 11 локалей (LocaleFormats) или свои (LocaleFormatter) без ext-intl; разбор строки"100.00"в сумму. - Курс
ExchangeRate— несократимая дробь: обратный курс, кросс-курс, наценка в базисных пунктах. - Семь режимов округления:
Up,Down,Ceiling,Floor,HalfUp,HalfDown,HalfEven. Отдельно — округление до шага наличных (CHF, CAD, AUD — шаг 5 минорных единиц). - Провайдеры курсов: таблица, цепочка, обратный курс, триангуляция через
опорную валюту (с инверсией ног, как у ЕЦБ), исторический курс на дату,
кэш с лимитом, срок жизни (TTL), наценка.
ResolvingRateProviderсам выбирает путь: та же валюта → прямой курс → обратный → кросс.PdoRateProviderчитает курсы из своей БД параметризованным запросом. - Фиды: XML ЕЦБ, XML Банка России (с номиналом), JSON вида
{"base":"EUR","rates":{...}}(Frankfurter, Fixer, Open Exchange Rates). Разбор без libxml и без float.FeedLoaderпроверяет URL до сети, а HTTP-клиент вы передаёте замыканием. - Сервисы курсов
FeedParsers: парсер ответа по имени сервиса из реестра florianv/swap — нацбанки Болгарии, Чехии, Турции, Узбекистана, Грузии, Беларуси, Румынии, Украины и коммерческие API (Fixer, currencylayer, Open Exchange Rates, Xignite, Forge, Cryptonator, coinlayer и др.). Свой формат описывается черезRecordFormatилиMapRateParser. - Котировка
RateQuote: bid, ask, средний курс, спред в базисных пунктах. - Пакетная конвертация
BatchConverter— генератор: память не растёт с длиной списка. - Комиссия в базисных пунктах
FeeCalculator. - Имена валют на языке
CurrencyNamesс запасным английским. - Безопасность: подпись курса HMAC-SHA256 со сравнением через
hash_equals(CWE-208), запретDOCTYPE/ENTITYи потоковых обёрток в фиде (XXE), список разрешённых хостов только по https и без userinfo.
Страница на каждую возможность с диаграммой — в wiki.
Сравнение с аналогами
Таблицы ниже собираются скриптом composer docs:sync из реальных прогонов.
Аналоги стоят в изолированном каталоге benchmarks/analogs и не попадают в
зависимости пакета.
Возможности
| Возможность | currency-converter | brick/money | moneyphp/money | florianv/swap | commerceguys/intl | alcohol/iso4217 | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| ISO 4217: код, номер, экспонента | да | да | да | — | да | да | currency-converter, brick/money, moneyphp/money, commerceguys/intl, alcohol/iso4217 |
| Исторические (выведенные) валюты | да | — | — | — | — | да | currency-converter, alcohol/iso4217 |
| Символ и имя валюты | да | да | — | — | да | да | currency-converter, brick/money, commerceguys/intl, alcohol/iso4217 |
| Сумма в минорных единицах без float | да | да | да | — | — | — | currency-converter, brick/money, moneyphp/money |
| Арифметика сумм | да | да | да | — | — | — | currency-converter, brick/money, moneyphp/money |
| Разложение суммы (allocate) | да | да | да | — | — | — | currency-converter, brick/money, moneyphp/money |
| Сравнение сумм | да | да | да | — | — | — | currency-converter, brick/money, moneyphp/money |
| Формат: символ, код, группировка разрядов | да | да | да | — | да | — | currency-converter, brick/money, moneyphp/money, commerceguys/intl |
| Курс как несократимая дробь | да | да | — | — | — | — | currency-converter, brick/money |
| Несколько режимов округления | да | да | да | — | — | — | currency-converter, brick/money, moneyphp/money |
| Округление до шага наличных | да | да | — | — | — | — | currency-converter, brick/money |
| Таблица курсов в памяти | да | да | да | да | — | — | currency-converter, brick/money, moneyphp/money, florianv/swap |
| Цепочка источников курса | да | да | — | да | — | — | currency-converter, brick/money, florianv/swap |
| Обратный курс | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Триангуляция через опорную валюту | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Исторический курс на дату | да | — | — | да | — | — | currency-converter, florianv/swap |
| Кэш курсов | да | да | — | да | — | — | currency-converter, brick/money, florianv/swap |
| Срок жизни курса (TTL) | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Наценка в базисных пунктах | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Разбор пары: прямой, обратный, кросс | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Курсы Европейского центрального банка | да | — | — | да | — | — | currency-converter, florianv/swap |
| Разбор XML Банка России | да | — | — | да | — | — | currency-converter, florianv/swap |
| Разбор JSON курсов | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Bid, ask, mid и спред | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Пакетная конвертация | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Комиссия в базисных пунктах | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Имя валюты на языке с запасным английским | да | — | — | — | да | — | currency-converter, commerceguys/intl |
| Минимум, максимум и сумма списка | да | да | да | — | — | — | currency-converter, brick/money, moneyphp/money |
| Среднее значение списка | да | — | да | — | — | — | currency-converter, moneyphp/money |
| Остаток и отношение двух сумм | да | — | да | — | — | — | currency-converter, moneyphp/money |
| Мультивалютная корзина с итогом | да | да | — | — | — | — | currency-converter, brick/money |
| Формат по правилам локали | да | да | да | — | да | — | currency-converter, brick/money, moneyphp/money, commerceguys/intl |
| Разбор строки в сумму | да | да | да | — | да | — | currency-converter, brick/money, moneyphp/money, commerceguys/intl |
| Свои валюты и криптовалюты | да | да | да | — | — | — | currency-converter, brick/money, moneyphp/money |
| Курсы из базы данных (PDO) | да | да | — | — | — | — | currency-converter, brick/money |
| Загрузка фида по URL | да | — | — | да | — | — | currency-converter, florianv/swap |
| Подпись курса HMAC-SHA256 | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Запрет XXE и allowlist только https | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Курсы нацбанков Болгарии, Чехии, Турции, Узбекистана, Грузии, Беларуси, Румынии, Украины | да | — | — | да | — | — | currency-converter, florianv/swap |
| Форматы коммерческих API курсов (Fixer, currencylayer, Open Exchange Rates, Xignite и др.) | да | — | — | да | — | — | currency-converter, florianv/swap |
| Курсы криптовалют от сервисов (coinlayer, Cryptonator) | да | — | — | да | — | — | currency-converter, florianv/swap |
| Реестр сервисов курсов по имени | да | — | — | да | — | — | currency-converter, florianv/swap |
Производительность, память и утечки
| Сценарий | currency-converter | brick/money | moneyphp/money | florianv/swap | commerceguys/intl | alcohol/iso4217 | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Справочник: экспонента USD, ops/s | 7011168 🏆 | 6123197 | 2772516 | н/д | 565699 | 70973 | currency-converter 🏆 |
| Конвертация 100.00 EUR → USD, ops/s | 651138 🏆 | 85098 | 245230 | н/д | н/д | н/д | currency-converter 🏆 |
| Чтение курса EUR/USD, ops/s | 7118252 🏆 | 6144540 | 2399885 | 87798 | н/д | н/д | currency-converter 🏆 |
| Память одного значения, байт | 80 🏆 | 248 | 112 | н/д | н/д | н/д | currency-converter 🏆 |
| Рост памяти после 20 000 выброшенных значений, байт | 376 🏆 | 376 🏆 | 376 🏆 | н/д | н/д | н/д | currency-converter, brick/money, moneyphp/money 🏆 |
PHP 8.1.34. Память — прирост memory_get_usage на новую сумму 100.00 USD после прогрева классов. Утечка — прирост после цикла и gc_collect_cycles. Справочники без денежной суммы (swap, intl, iso4217) в замер памяти не входят: у них нет объекта суммы. Библиотеки без операции помечаются как неподдерживаемые и в зачёт строки не входят.
Качество кода
| Проверка | currency-converter | brick/money | moneyphp/money | florianv/swap | commerceguys/intl | alcohol/iso4217 | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| PHPStan level max, ошибок | 0 🏆 | 25 | 78 | 32 | 210 | 0 🏆 | currency-converter, alcohol/iso4217 🏆 |
| PHPCS PSR-12, нарушений | 0 🏆 | 146 | 16 | 5 | 18 | 8 | currency-converter 🏆 |
| Infection MSI | 100% 🏆 | не публикуется | не публикуется | не публикуется | не публикуется | не публикуется | currency-converter 🏆 |
| Покрытие строк | 100% 🏆 | не публикуется | не публикуется | не публикуется | не публикуется | не публикуется | currency-converter 🏆 |
PHPStan и PHPCS прогнаны одной командой composer bench:quality по исходникам каждого пакета с одинаковыми правилами (PHPStan level max без baseline, PHPCS PSR-12). Аналоги не публикуют MSI и покрытие, а их тесты не входят в дистрибутив, поэтому эти строки у них не измерены.
Безопасность
| Стандарт | currency-converter | brick/money | moneyphp/money | florianv/swap | commerceguys/intl | alcohol/iso4217 | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| SECURITY.md | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| composer audit в CI | да 🏆 | не измерялось | не измерялось | не измерялось | не измерялось | не измерялось | currency-converter 🏆 |
| roave/security-advisories | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| CWE-208: hash_equals для подписи | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Запрет XXE (DOCTYPE, ENTITY) | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
| Allowlist https без userinfo | да 🏆 | — | — | — | — | — | currency-converter 🏆 |
Наличие SECURITY.md у аналогов проверено в их установленных деревьях: файла нет. Остальные строгие меры относятся к разбору курса и в справочниках валют не требуются. OpenSSF Scorecard для GitVerse-репозитория отдельным числом не публикуется: политика и проверки перечислены в SECURITY.md.
Плюсы и минусы
Плюсы
- Возможностей больше, чем у пяти аналогов вместе: каждая строка таблицы возможностей закрыта.
- На всех замерах скорости и памяти одной суммы — первое место; утечек нет, как и у аналогов.
- Ни одного float на пути суммы и курса. Переполнение проверяется заранее.
- Ноль зависимостей, кроме PHP 8.1.
- 0 ошибок PHPStan level max и Psalm errorLevel 1, 0 нарушений PHPMD и PSR-12, покрытие строк 100%, Infection MSI 100%.
Минусы
- Пакет моложе аналогов.
- Пакет пока менее распространён: скачиваний и зависимых проектов меньше.
- Часы для TTL — замыкание
now(): int, а не готовыйClockInterface: граница слоёв пакета запрещает внешние классы в коде. Сcloud-castle/clockподключение — одна строка:fn (): int => $clock->now()->getTimestamp().
Где применять
Рекомендуется
- Платёжные и бухгалтерские системы, где сумма должна сойтись до копейки.
- Мультивалютные корзины и прайсы интернет-магазинов.
- Сверка выписок и реестров с курсами ЕЦБ или Банка России.
- Пакетный пересчёт больших списков: генератор держит память ровной.
- Сервисы, которые принимают курс из внешнего фида и обязаны проверить подпись и источник.
Не подходит
- Произвольная точность сверх 64-битного
int(суммы больше 9 223 372 036 854 775 807 минорных единиц) — здесь нужна арифметика больших чисел. - Крупные суммы в монетах с экспонентой 18 (ETH в wei): предел 64-битного целого — около 9.22 монеты. Биткоин (экспонента 8) — до 92 миллиардов BTC.
Разработка
composer install
composer check # линтер, статанализ, PHPCS, PHPMD, deptrac, тесты
composer infection # мутационное тестирование
composer bench # замеры против аналогов
composer bench:quality # PHPStan и PHPCS по исходникам аналогов
composer docs:sync # пересобрать таблицы во всех языках и в wiki
Документация
- Wiki: главная
- История изменений
- Переход между версиями
- Участие в разработке
- Политика безопасности
- Поддержка
Лицензия
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano