lemonade / component_currency
Typed PHP currency conversion library using Czech National Bank exchange rates, filesystem cache, fallback values, and a lightweight hexagonal architecture.
Requires
- php: >=8.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/php-code-coverage: ^10.1
- phpunit/phpunit: ^10.5 || ^11.0
README
Lemonade Currency Conversion Library
Lemonade Currency Conversion Library provides a general-purpose currency converter using the Czech National Bank (CNB) as the primary data source.
The library exposes a simple developer-friendly API while internally using a layered, hexagonal-style architecture. This keeps the public API stable and easy to use, while infrastructure concerns such as CNB fetching, parsing, caching, and time resolution stay isolated behind explicit ports and adapters.
Features
- Supports PHP 8.1+
- Fully typed and optimized for static analysis with PHPStan Level 10, strict rules, and bleeding edge
- Provides exchange rates and currency values for supported currencies
- Uses the Czech National Bank (CNB) as the primary data source
- Caches CNB yearly exchange-rate data in the filesystem
- Falls back to default values when CNB data is unavailable
- Supports nearest previous available rate lookup
- Automatically adjusts to CNB's publishing schedule:
- Exchange rates are published on working days after 14:30.
- Before 14:30, the previous day's rates are used as the current rate.
- Provides a backward-compatible facade API for simple usage
Supported Data Sources
- Czech National Bank (CNB)
Supported Currencies
- CZK - Czech Republic
- EUR - Eurozone
- HUF - Hungary
- PLN - Poland
- GBP - Great Britain
- USD - United States
Installation
Use Composer to install the library:
composer require lemonade/component_currency
Architecture
The library is organized using a lightweight hexagonal architecture.
src/
Domain/
CurrencyCode.php
CurrencyCatalog.php
DefaultCurrencyRates.php
ExchangeRateTable.php
Application/
CurrencyRateService.php
CurrencyRateServiceFactory.php
Port/
Clock.php
ExchangeRateCache.php
ExchangeRateSource.php
Infrastructure/
Cache/
FileExchangeRateCache.php
Clock/
SystemClock.php
Cnb/
CnbExchangeRateSource.php
CnbRateParser.php
LineSplitter.php
RegexLineSplitter.php
CurrencyRate.php
CurrencyMarket.php
CurrencyList.php
CurrencyStorage.php
CurrencyData.php
Domain
The domain layer contains the core currency model and rules:
- supported currency codes
- default currency values
- currency metadata such as symbols and names
- parsed exchange-rate table representation
It does not depend on CNB, filesystem, HTTP, or runtime environment.
Application
The application layer contains the main use case:
- resolving the requested date
- applying CNB publishing-time rules
- reading rates from cache
- fetching rates from the configured source
- parsing CNB data
- falling back to default values when needed
The primary service is:
Lemonade\Currency\Application\CurrencyRateService
Ports
Ports define contracts for external concerns:
Lemonade\Currency\Port\ExchangeRateSource Lemonade\Currency\Port\ExchangeRateCache Lemonade\Currency\Port\Clock
This allows custom implementations for fetching rates, caching data, or controlling time in tests.
Infrastructure
Infrastructure contains concrete adapters:
CnbExchangeRateSourcefetches CNB dataCnbRateParserparses CNB text dataFileExchangeRateCachestores CNB data in the filesystemSystemClockprovides current runtime timeRegexLineSplittersplits CNB text data into lines
Facade API
The public facade classes remain available for simple usage and backward compatibility:
Lemonade\Currency\CurrencyRate Lemonade\Currency\CurrencyMarket Lemonade\Currency\CurrencyList
Usage
Basic static API
use DateTimeImmutable; use Lemonade\Currency\CurrencyRate; // Get the ratio of a foreign currency against the local currency. $eurRatio = CurrencyRate::getRatio(currency: 'EUR'); // Get the value of a foreign currency against CZK. $usdValue = CurrencyRate::getValue(currency: 'USD'); // Resolve a rate for a specific date. $eurRatioForDate = CurrencyRate::getRatio( currency: 'EUR', date: new DateTimeImmutable('2023-12-01') ); $usdValueForDate = CurrencyRate::getValue( currency: 'USD', date: new DateTimeImmutable('2023-12-01') );
Shortcut methods
use DateTimeImmutable; use Lemonade\Currency\CurrencyRate; $eur = CurrencyRate::eur(); $usd = CurrencyRate::usd(); $gbp = CurrencyRate::gbp(); $pln = CurrencyRate::pln(); $huf = CurrencyRate::huf(); $eurForDate = CurrencyRate::eur(new DateTimeImmutable('2023-12-01'));
Market object API
use DateTimeImmutable; use Lemonade\Currency\CurrencyMarket; $market = new CurrencyMarket(new DateTimeImmutable('2023-12-01')); $eurRatio = $market->getRatio(currency: 'EUR'); $usdValue = $market->getValue(currency: 'USD');
Currency metadata
use Lemonade\Currency\CurrencyList; $currencies = CurrencyList::getCurrencies(); $symbols = CurrencyList::getCurrencySymbolList(); $eurSymbol = CurrencyList::getCurrencySymbol('EUR'); $eurName = CurrencyList::getCurrencyLanguageName('EUR');
Advanced Usage
Custom storage directory
By default, the library stores CNB cache files in:
storage/export/cnb
You can create the application service manually with a custom storage directory:
use Lemonade\Currency\Application\CurrencyRateServiceFactory; $service = CurrencyRateServiceFactory::createDefault( storageDirectory: __DIR__ . '/var/cache/cnb' ); $value = $service->getValue('EUR');
Custom cache, source, parser, or clock
For full control, instantiate the application service directly:
use Lemonade\Currency\Application\CurrencyRateService; use Lemonade\Currency\Domain\DefaultCurrencyRates; use Lemonade\Currency\Infrastructure\Cache\FileExchangeRateCache; use Lemonade\Currency\Infrastructure\Clock\SystemClock; use Lemonade\Currency\Infrastructure\Cnb\CnbExchangeRateSource; use Lemonade\Currency\Infrastructure\Cnb\CnbRateParser; $service = new CurrencyRateService( cache: new FileExchangeRateCache(__DIR__ . '/var/cache/cnb'), source: new CnbExchangeRateSource(), parser: new CnbRateParser(), defaults: new DefaultCurrencyRates(), clock: new SystemClock(), ); $value = $service->getValue('EUR');
This is useful when integrating the library into applications that already have their own cache layer, HTTP transport, or deterministic clock.
Custom exchange-rate source
Implement the ExchangeRateSource port to fetch data from a different source.
use Lemonade\Currency\Port\ExchangeRateSource; final class CustomExchangeRateSource implements ExchangeRateSource { public function fetchYear(int $year): string { // Return raw yearly exchange-rate data in the format expected by the configured parser. return 'Datum|1 EUR' . PHP_EOL . '2.1.2024|24,725'; } public function getUrl(int $year): string { return 'custom://currency-rates/' . $year; } }
CNB Data Notes
CNB yearly exchange-rate data uses multipliers in the header, for example:
Datum|1 EUR|100 HUF|1 PLN 2.1.2024|24,725|6,505|5,688
The parser normalizes values to a single currency unit. For example, 100 HUF with value 6,505 is stored as 0.06505 per 1 HUF.
Configuration
The default filesystem cache directory is:
storage/export/cnb
Ensure this directory is writable by the PHP process when using the default service factory.
Testing
Run unit tests:
vendor/bin/phpunit
Run static analysis:
vendor/bin/phpstan analyse
Quality
The library is designed for:
- strict typing
- PHPStan Level 10
- strict rules
- bleeding-edge static analysis
- isolated domain logic
- testable infrastructure adapters
- stable backward-compatible facade API
Contributing
Feel free to submit issues or create pull requests to improve this library.
License
This library is licensed under the MIT License. See the LICENSE file for details.