Search by

cloud-castle / currency-converter

alex-4-17

Integer currency conversion for PHP 8.1: ISO 4217, rational exchange rates, ECB/CBR/national bank and API feeds, signature checks, no floating point.

v1.0.0 2026-10-06 07:19 UTC

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

CloudCastle Currency Converter

Packagist Version PHP Version License Downloads Monthly downloads

Репозиторий CI Задачи Релизы Wiki

PHPStan PHPMD PHPCS Psalm coverage Infection MSI OpenSSF Scorecard

Конвертация валют для 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-converterbrick/moneymoneyphp/moneyflorianv/swapcommerceguys/intlalcohol/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-converterbrick/moneymoneyphp/moneyflorianv/swapcommerceguys/intlalcohol/iso4217🏆 Победитель
Справочник: экспонента USD, ops/s7011168 🏆61231972772516н/д56569970973currency-converter 🏆
Конвертация 100.00 EUR → USD, ops/s651138 🏆85098245230н/дн/дн/дcurrency-converter 🏆
Чтение курса EUR/USD, ops/s7118252 🏆6144540239988587798н/дн/дcurrency-converter 🏆
Память одного значения, байт80 🏆248112н/дн/дн/д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-converterbrick/moneymoneyphp/moneyflorianv/swapcommerceguys/intlalcohol/iso4217🏆 Победитель
PHPStan level max, ошибок0 🏆2578322100 🏆currency-converter, alcohol/iso4217 🏆
PHPCS PSR-12, нарушений0 🏆146165188currency-converter 🏆
Infection MSI100% 🏆не публикуетсяне публикуетсяне публикуетсяне публикуетсяне публикуетсяcurrency-converter 🏆
Покрытие строк100% 🏆не публикуетсяне публикуетсяне публикуетсяне публикуетсяне публикуетсяcurrency-converter 🏆

PHPStan и PHPCS прогнаны одной командой composer bench:quality по исходникам каждого пакета с одинаковыми правилами (PHPStan level max без baseline, PHPCS PSR-12). Аналоги не публикуют MSI и покрытие, а их тесты не входят в дистрибутив, поэтому эти строки у них не измерены.

Безопасность

Стандартcurrency-converterbrick/moneymoneyphp/moneyflorianv/swapcommerceguys/intlalcohol/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

Документация

Лицензия

MIT

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano