diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
new file mode 100644
index 0000000..748db18
--- /dev/null
+++ b/.github/workflows/phpunit.yml
@@ -0,0 +1,39 @@
+name: PHPUnit Tests
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: [7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ coverage: none
+ tools: composer
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ~/.composer/cache
+ key: composer-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ composer-${{ matrix.php-version }}-
+
+ - name: Install dependencies
+ run: composer update --no-interaction --prefer-dist
+
+ - name: Run tests
+ run: composer test
diff --git a/.gitignore b/.gitignore
index 281fbe8..bc06054 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ composer.lock
phpunit.xml
.php_cs.cache
.phpunit.result.cache
+
+.idea/
diff --git a/.travis.yml b/.travis.yml
index 334c65d..94f7e52 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,7 @@ matrix:
- php: 8.1
- php: 8.2
- php: 8.3
+ - php: 8.4
cache:
directories:
diff --git a/README.md b/README.md
index 59b0bb0..9790277 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
# Exchanger
-[](https://travis-ci.org/florianv/exchanger)
-[](https://packagist.org/packages/florianv/exchanger)
-[](https://packagist.org/packages/florianv/exchanger)
+**This is a fork of [florianv/exchanger](https://github.com/florianv/exchanger) for the use in Part-DB.**
Exchanger is a PHP framework to work with currency exchange rates from various services such as
**[Fixer](https://fixer.io)**, **[Currency Data](https://currencylayer.com)**
@@ -34,37 +32,39 @@ The documentation for the current branch can be found [here](https://github.com/
Here is the complete list of the currently implemented services:
-| Service | Base Currency | Quote Currency | Historical |
-|---------------------------------------------------------------------------|----------------------|----------------|----------------|
-| [Fixer](https://fixer.io) | EUR (free, no SSL), * (paid) | * | Yes |
-| [Currency Data](https://currencylayer.com) | USD (free), * (paid) | * | Yes |
-| [Exchange Rates Data](https://exchangeratesapi.io) | USD (free), * (paid) | * | Yes |
-| [Abstract](https://www.abstractapi.com) | * | * | Yes |
-| [coinlayer](https://coinlayer.com) | * Crypto (Limited standard currencies) | * Crypto (Limited standard currencies) | Yes |
-| [Fixer](https://fixer.io) | EUR (free, no SSL), * (paid) | * | Yes |
-| [currencylayer](https://currencylayer.com) | USD (free), * (paid) | * | Yes |
-| [exchangeratesapi](https://exchangeratesapi.io) | USD (free), * (paid) | * | Yes |
-| [European Central Bank](https://www.ecb.europa.eu/home/html/index.en.html) | EUR | * | Yes |
-| [National Bank of Georgia](https://nbg.gov.ge) | * | GEL | Yes |
-| [National Bank of the Republic of Belarus](https://www.nbrb.by) | * | BYN (from 01-07-2016),
BYR (01-01-2000 - 30-06-2016),
BYB (25-05-1992 - 31-12-1999) | Yes |
-| [National Bank of Romania](http://www.bnr.ro) | RON, AED, AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EGP, EUR, GBP, HRK, HUF, INR, JPY, KRW, MDL, MXN, NOK, NZD, PLN, RSD, RUB, SEK, TRY, UAH, USD, XAU, XDR, ZAR | RON, AED, AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EGP, EUR, GBP, HRK, HUF, INR, JPY, KRW, MDL, MXN, NOK, NZD, PLN, RSD, RUB, SEK, TRY, UAH, USD, XAU, XDR, ZAR | Yes |
-| [National Bank of Ukranie](https://bank.gov.ua) | * | UAH | Yes |
-| [Central Bank of the Republic of Turkey](http://www.tcmb.gov.tr) | * | TRY | Yes |
-| [Central Bank of the Republic of Uzbekistan](https://cbu.uz) | * | UZS | Yes |
-| [Central Bank of the Czech Republic](https://www.cnb.cz) | * | CZK | Yes |
-| [Central Bank of Russia](https://cbr.ru) | * | RUB | Yes |
-| [Bulgarian National Bank](http://bnb.bg) | * | BGN | Yes |
-| [WebserviceX](http://www.webservicex.net) | * | * | No |
-| [1Forge](https://1forge.com) | * (free but limited or paid) | * (free but limited or paid) | No |
-| [Cryptonator](https://www.cryptonator.com) | * Crypto (Limited standard currencies) | * Crypto (Limited standard currencies) | No |
-| [CurrencyDataFeed](https://currencydatafeed.com) | * (free but limited or paid) | * (free but limited or paid) | No |
-| [Open Exchange Rates](https://openexchangerates.org) | USD (free), * (paid) | * | Yes |
-| [Xignite](https://www.xignite.com) | * | * | Yes |
-| [Currency Converter API](https://www.currencyconverterapi.com) | * | * | Yes (free but limited or paid) |
-| [xChangeApi.com](https://xchangeapi.com) | * | * | Yes |
-| [fastFOREX.io](https://www.fastforex.io) | USD (free), * (paid) | * | No |
-| [exchangerate.host](https://www.exchangerate.host) | * | * | Yes |
-| Array | * | * | Yes |
+| Service | Base Currency | Quote Currency | Historical |
+|----------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
+| [Fixer](https://fixer.io) | EUR (free, no SSL), * (paid) | * | Yes |
+| [Currency Data](https://currencylayer.com) | USD (free), * (paid) | * | Yes |
+| [Exchange Rates Data](https://exchangeratesapi.io) | USD (free), * (paid) | * | Yes |
+| [Abstract](https://www.abstractapi.com) | * | * | Yes |
+| [coinlayer](https://coinlayer.com) | * Crypto (Limited standard currencies) | * Crypto (Limited standard currencies) | Yes |
+| [Fixer](https://fixer.io) | EUR (free, no SSL), * (paid) | * | Yes |
+| [currencylayer](https://currencylayer.com) | USD (free), * (paid) | * | Yes |
+| [exchangeratesapi](https://exchangeratesapi.io) | USD (free), * (paid) | * | Yes |
+| [European Central Bank](https://www.ecb.europa.eu/home/html/index.en.html) | EUR | * | Yes |
+| [National Bank of Georgia](https://nbg.gov.ge) | * | GEL | Yes |
+| [National Bank of the Republic of Belarus](https://www.nbrb.by) | * | BYN (from 01-07-2016),
BYR (01-01-2000 - 30-06-2016),
BYB (25-05-1992 - 31-12-1999) | Yes |
+| [National Bank of Romania](http://www.bnr.ro) | RON, AED, AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EGP, EUR, GBP, HRK, HUF, INR, JPY, KRW, MDL, MXN, NOK, NZD, PLN, RSD, RUB, SEK, TRY, UAH, USD, XAU, XDR, ZAR | RON, AED, AUD, BGN, BRL, CAD, CHF, CNY, CZK, DKK, EGP, EUR, GBP, HRK, HUF, INR, JPY, KRW, MDL, MXN, NOK, NZD, PLN, RSD, RUB, SEK, TRY, UAH, USD, XAU, XDR, ZAR | Yes |
+| [National Bank of Ukranie](https://bank.gov.ua) | * | UAH | Yes |
+| [Central Bank of the Republic of Turkey](http://www.tcmb.gov.tr) | * | TRY | Yes |
+| [Central Bank of the Republic of Uzbekistan](https://cbu.uz) | * | UZS | Yes |
+| [Central Bank of the Czech Republic](https://www.cnb.cz) | * | CZK | Yes |
+| [Central Bank of Russia](https://cbr.ru) | * | RUB | Yes |
+| [Bulgarian National Bank](http://bnb.bg) | * | BGN | Yes |
+| [WebserviceX](http://www.webservicex.net) | * | * | No |
+| [1Forge](https://1forge.com) | * (free but limited or paid) | * (free but limited or paid) | No |
+| [Cryptonator](https://www.cryptonator.com) | * Crypto (Limited standard currencies) | * Crypto (Limited standard currencies) | No |
+| [CurrencyDataFeed](https://currencydatafeed.com) | * (free but limited or paid) | * (free but limited or paid) | No |
+| [Open Exchange Rates](https://openexchangerates.org) | USD (free), * (paid) | * | Yes |
+| [Xignite](https://www.xignite.com) | * | * | Yes |
+| [Currency Converter API](https://www.currencyconverterapi.com) | * | * | Yes (free but limited or paid) |
+| [xChangeApi.com](https://xchangeapi.com) | * | * | Yes |
+| [fastFOREX.io](https://www.fastforex.io) | USD (free), * (paid) | * | No |
+| [exchangerate.host](https://www.exchangerate.host) | * | * | Yes |
+| [Frankfurter.dev](https://frankfurter.dev/) | * | * | Yes |
+| [fawazahmed0 Exchange-API](https://github.com/fawazahmed0/exchange-api) | * | * | Yes (very limited) |
+| Array | * | * | Yes |
## Credits
diff --git a/composer.json b/composer.json
index f549972..38b0c57 100644
--- a/composer.json
+++ b/composer.json
@@ -1,15 +1,19 @@
{
- "name": "florianv/exchanger",
+ "name": "part-db/exchanger",
"type": "library",
- "description": "Currency exchange rates framework for PHP",
+ "description": "Fork of florianv/exchanger, a library to convert currencies using different exchange rate providers. Modernized to be compatible with Part-DB.",
"keywords": ["currency", "money", "rate", "conversion", "exchange rates"],
- "homepage": "https://github.com/florianv/exchanger",
+ "homepage": "https://github.com/Part-DB/exchanger",
"license": "MIT",
"authors": [
{
"name": "Florian Voutzinos",
"email": "florian@voutzinos.com",
"homepage": "https://voutzinos.com/"
+ },
+ {
+ "name": "Jan Böhmer",
+ "email": "mail@jan-boehmer.de"
}
],
"autoload": {
@@ -31,14 +35,14 @@
"php-http/discovery": "^1.6",
"psr/http-factory": "^1.0.2",
"php-http/client-implementation": "^1.0",
- "psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
- "symfony/http-client": "^5.4",
- "php-http/message-factory": "^1.1"
+ "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
- "phpunit/phpunit": "^7 || ^8 || ^9.4",
+ "phpunit/phpunit": "^7 || ^8 || ^9.4 || ^10.5",
"php-http/message": "^1.7",
"php-http/mock-client": "^1.0",
+ "php-http/message-factory": "^1.1",
+ "symfony/http-client": "^5.4 || ^6.4 || ^7.0",
"nyholm/psr7": "^1.0"
},
"scripts": {
diff --git a/src/Exchanger.php b/src/Exchanger.php
index aa06e07..42cf6ac 100755
--- a/src/Exchanger.php
+++ b/src/Exchanger.php
@@ -56,7 +56,7 @@ final class Exchanger implements ExchangeRateProviderContract
* @param CacheInterface|null $cache
* @param array $options
*/
- public function __construct(ExchangeRateServiceContract $service, CacheInterface $cache = null, array $options = [])
+ public function __construct(ExchangeRateServiceContract $service, ?CacheInterface $cache = null, array $options = [])
{
$this->service = $service;
$this->cache = $cache;
diff --git a/src/Service/CentralBankOfCzechRepublic.php b/src/Service/CentralBankOfCzechRepublic.php
index b40b6cf..0ee263d 100755
--- a/src/Service/CentralBankOfCzechRepublic.php
+++ b/src/Service/CentralBankOfCzechRepublic.php
@@ -77,6 +77,9 @@ private function doCreateRate(ExchangeRateQuery $exchangeQuery, DateTimeInterfac
$currencyPair = $exchangeQuery->getCurrencyPair();
$content = $this->request($this->buildUrl($requestedDate));
+ //Normalize line endings
+ $content = str_replace(["\r\n", "\r"], "\n", $content);
+
$lines = explode("\n", $content);
if (!$date = \DateTime::createFromFormat(self::DATE_FORMAT, $this->parseDate($lines[0]))) {
diff --git a/src/Service/CentralBankOfRepublicTurkey.php b/src/Service/CentralBankOfRepublicTurkey.php
index 1da8371..82125cd 100755
--- a/src/Service/CentralBankOfRepublicTurkey.php
+++ b/src/Service/CentralBankOfRepublicTurkey.php
@@ -70,7 +70,7 @@ public function supportQuery(ExchangeRateQuery $exchangeRateQuery): bool
*
* @throws UnsupportedCurrencyPairException
*/
- private function doCreateRate(ExchangeRateQuery $exchangeQuery, DateTimeInterface $requestedDate = null): ExchangeRate
+ private function doCreateRate(ExchangeRateQuery $exchangeQuery, ?DateTimeInterface $requestedDate = null): ExchangeRate
{
$currencyPair = $exchangeQuery->getCurrencyPair();
$content = $this->request($this->buildUrl($requestedDate));
@@ -97,7 +97,7 @@ private function doCreateRate(ExchangeRateQuery $exchangeQuery, DateTimeInterfac
*
* @return string
*/
- private function buildUrl(DateTimeInterface $requestedDate = null): string
+ private function buildUrl(?DateTimeInterface $requestedDate = null): string
{
if (null === $requestedDate) {
$fileName = 'today';
diff --git a/src/Service/CentralBankOfRepublicUzbekistan.php b/src/Service/CentralBankOfRepublicUzbekistan.php
index 7836ea7..470aef3 100644
--- a/src/Service/CentralBankOfRepublicUzbekistan.php
+++ b/src/Service/CentralBankOfRepublicUzbekistan.php
@@ -77,7 +77,7 @@ protected function getHistoricalExchangeRate(HistoricalExchangeRateQuery $exchan
* @throws UnsupportedCurrencyPairException
* @throws Exception
*/
- private function doCreateRate(ExchangeRateQuery $exchangeQuery, DateTimeInterface $requestedDate = null): ExchangeRate
+ private function doCreateRate(ExchangeRateQuery $exchangeQuery, ?DateTimeInterface $requestedDate = null): ExchangeRate
{
$currencyPair = $exchangeQuery->getCurrencyPair();
@@ -106,7 +106,7 @@ private function doCreateRate(ExchangeRateQuery $exchangeQuery, DateTimeInterfac
*
* @return string
*/
- private function buildUrl(DateTimeInterface $requestedDate = null): string
+ private function buildUrl(?DateTimeInterface $requestedDate = null): string
{
$date = '';
if (!is_null($requestedDate)) {
diff --git a/src/Service/EuropeanCentralBank.php b/src/Service/EuropeanCentralBank.php
index 1588cd6..6d7d404 100755
--- a/src/Service/EuropeanCentralBank.php
+++ b/src/Service/EuropeanCentralBank.php
@@ -13,6 +13,10 @@
namespace Exchanger\Service;
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use Exception;
use Exchanger\Contract\ExchangeRateQuery;
use Exchanger\Contract\HistoricalExchangeRateQuery;
use Exchanger\Exception\UnsupportedCurrencyPairException;
@@ -29,11 +33,11 @@ final class EuropeanCentralBank extends HttpService
{
use SupportsHistoricalQueries;
- const DAILY_URL = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
+ private const DAILY_URL = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
- const HISTORICAL_URL_LIMITED_TO_90_DAYS_BACK = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml';
+ private const HISTORICAL_URL_LIMITED_TO_90_DAYS_BACK = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml';
- const HISTORICAL_URL_OLDER_THAN_90_DAYS = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml';
+ private const HISTORICAL_URL_OLDER_THAN_90_DAYS = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml';
/**
* {@inheritdoc}
@@ -48,7 +52,7 @@ protected function getLatestExchangeRate(ExchangeRateQuery $exchangeQuery): Exch
$quoteCurrency = $currencyPair->getQuoteCurrency();
$elements = $element->xpath('//xmlns:Cube[@currency="'.$quoteCurrency.'"]/@rate');
- $date = new \DateTime((string) $element->xpath('//xmlns:Cube[@time]/@time')[0]);
+ $date = new DateTime((string) $element->xpath('//xmlns:Cube[@time]/@time')[0]);
if (empty($elements) || !$date) {
throw new UnsupportedCurrencyPairException($currencyPair, $this);
@@ -72,13 +76,22 @@ protected function getHistoricalExchangeRate(HistoricalExchangeRateQuery $exchan
$formattedDate = $exchangeQuery->getDate()->format('Y-m-d');
$quoteCurrency = $currencyPair->getQuoteCurrency();
- $elements = $element->xpath('//xmlns:Cube[@time="'.$formattedDate.'"]/xmlns:Cube[@currency="'.$quoteCurrency.'"]/@rate');
-
- if (empty($elements)) {
- if (empty($element->xpath('//xmlns:Cube[@time="'.$formattedDate.'"]'))) {
+ /**
+ * if rate do not exist for actual date, try get prev day rate, while ge it
+ */
+ $prevDays = 0;
+ while (empty($element->xpath('//xmlns:Cube[@time="'.$formattedDate.'"]'))) {
+ $prevDays ++;
+ if ($prevDays > 7) {
throw new UnsupportedDateException($exchangeQuery->getDate(), $this);
}
+ $formattedDate = (clone $exchangeQuery->getDate())
+ ->sub(new DateInterval('P'.$prevDays.'D'))
+ ->format('Y-m-d');
+ }
+ $elements = $element->xpath('//xmlns:Cube[@time="'.$formattedDate.'"]/xmlns:Cube[@currency="'.$quoteCurrency.'"]/@rate');
+ if (empty($elements)) {
throw new UnsupportedCurrencyPairException($currencyPair, $this);
}
@@ -102,15 +115,15 @@ public function getName(): string
}
/**
- * @param \DateTimeInterface $date
+ * @param DateTimeInterface $date
*
* @return string
*
- * @throws \Exception
+ * @throws Exception
*/
- private function getHistoricalUrl(\DateTimeInterface $date): string
+ private function getHistoricalUrl(DateTimeInterface $date): string
{
- $dateDiffInDays = $date->diff(new \DateTime('now'))->days;
+ $dateDiffInDays = $date->diff(new DateTime('now'))->days;
if ($dateDiffInDays > 90) {
return self::HISTORICAL_URL_OLDER_THAN_90_DAYS;
}
diff --git a/src/Service/FawazahmedCurrencyAPI.php b/src/Service/FawazahmedCurrencyAPI.php
new file mode 100644
index 0000000..9e68b5f
--- /dev/null
+++ b/src/Service/FawazahmedCurrencyAPI.php
@@ -0,0 +1,56 @@
+getCurrencyPair();
+ $base = strtolower($currencyPair->getBaseCurrency());
+ $quote = strtolower($currencyPair->getQuoteCurrency());
+
+ if ($exchangeQuery instanceof HistoricalExchangeRateQuery) {
+ $date = $exchangeQuery->getDate()->format('Y-m-d');
+ $url = sprintf(self::URL_TEMPLATE, $date, $base);
+ } else {
+ $url = sprintf(self::URL_TEMPLATE,'latest', $base);
+ }
+
+ $content = $this->request($url);
+ $data = json_decode($content, true);
+
+ if (!isset($data[$base]) || !isset($data[$base][$quote])) {
+ throw new \Exchanger\Exception\UnsupportedCurrencyPairException($currencyPair, $this);
+ }
+
+ $rate = (float)$data[$base][$quote];
+ $date = new \DateTime($data['date']);
+
+ return $this->createRate($currencyPair, $rate, $date);
+ }
+
+ public function supportQuery(ExchangeRateQuery $exchangeQuery): bool
+ {
+ return !($exchangeQuery instanceof HistoricalExchangeRateQuery && $exchangeQuery->getDate() < new \DateTime('2025-01-01'));
+ }
+
+ public function getName(): string
+ {
+ return "fawazahmed_currency_api";
+ }
+}
diff --git a/src/Service/Frankfurter.php b/src/Service/Frankfurter.php
new file mode 100644
index 0000000..455220e
--- /dev/null
+++ b/src/Service/Frankfurter.php
@@ -0,0 +1,64 @@
+getCurrencyPair();
+ return in_array($currencyPair->getBaseCurrency(), self::SUPPORTED_CURRENCIES) &&
+ in_array($currencyPair->getQuoteCurrency(), self::SUPPORTED_CURRENCIES);
+ }
+
+ public function getName(): string
+ {
+ return 'frankfurter';
+ }
+
+ public function getExchangeRate(ExchangeRateQuery $exchangeQuery): ExchangeRate
+ {
+ $currencyPair = $exchangeQuery->getCurrencyPair();
+ $base = $currencyPair->getBaseCurrency();
+ $quote = $currencyPair->getQuoteCurrency();
+
+ if ($exchangeQuery instanceof HistoricalExchangeRateQueryContract) {
+ $date = $exchangeQuery->getDate()->format('Y-m-d');
+ $url = self::BASE_URL . "{$date}?base={$base}&symbols={$quote}";
+ } else {
+ $url = self::BASE_URL . "latest?base={$base}&symbols={$quote}";
+ }
+
+ $content = $this->request($url);
+ $data = json_decode($content, true);
+
+ if (!isset($data['rates'][$quote])) {
+ throw new UnsupportedCurrencyPairException($currencyPair, $this);
+ }
+
+ $rate = (float)$data['rates'][$quote];
+ $date = new \DateTime($data['date']);
+
+ return $this->createRate($currencyPair, $rate, $date);
+ }
+}
diff --git a/src/Service/HttpService.php b/src/Service/HttpService.php
index c24daf0..a484394 100644
--- a/src/Service/HttpService.php
+++ b/src/Service/HttpService.php
@@ -14,8 +14,10 @@
namespace Exchanger\Service;
use Http\Client\HttpClient;
+use Http\Discovery\Exception\NotFoundException;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
+use Http\Discovery\Psr18ClientDiscovery;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
@@ -47,10 +49,14 @@ abstract class HttpService extends Service
* @param RequestFactoryInterface|null $requestFactory
* @param array $options
*/
- public function __construct($httpClient = null, RequestFactoryInterface $requestFactory = null, array $options = [])
+ public function __construct($httpClient = null, ?RequestFactoryInterface $requestFactory = null, array $options = [])
{
if (null === $httpClient) {
- $httpClient = HttpClientDiscovery::find();
+ try {
+ $httpClient = Psr18ClientDiscovery::find();
+ } catch (NotFoundException $e) {
+ $httpClient = HttpClientDiscovery::find();
+ }
} else {
if (!$httpClient instanceof ClientInterface && !$httpClient instanceof HttpClient) {
throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface');
diff --git a/src/Service/NationalBankOfGeorgia.php b/src/Service/NationalBankOfGeorgia.php
index f4becdf..813920c 100644
--- a/src/Service/NationalBankOfGeorgia.php
+++ b/src/Service/NationalBankOfGeorgia.php
@@ -77,7 +77,7 @@ protected function getHistoricalExchangeRate(HistoricalExchangeRateQuery $exchan
* @throws UnsupportedCurrencyPairException
* @throws Exception
*/
- private function doCreateRate(ExchangeRateQuery $exchangeQuery, DateTimeInterface $requestedDate = null): ExchangeRate
+ private function doCreateRate(ExchangeRateQuery $exchangeQuery, ?DateTimeInterface $requestedDate = null): ExchangeRate
{
$currencyPair = $exchangeQuery->getCurrencyPair();
@@ -106,7 +106,7 @@ private function doCreateRate(ExchangeRateQuery $exchangeQuery, DateTimeInterfac
*
* @return string
*/
- private function buildUrl(DateTimeInterface $requestedDate = null): string
+ private function buildUrl(?DateTimeInterface $requestedDate = null): string
{
$date = '';
if (!is_null($requestedDate)) {
diff --git a/src/Service/NationalBankOfRepublicBelarus.php b/src/Service/NationalBankOfRepublicBelarus.php
index 6b16932..d171205 100644
--- a/src/Service/NationalBankOfRepublicBelarus.php
+++ b/src/Service/NationalBankOfRepublicBelarus.php
@@ -81,7 +81,7 @@ public function supportQuery(ExchangeRateQuery $exchangeQuery, bool $ignoreSuppo
*
* @return int|false
*/
- private static function detectPeriodicity(string $baseCurrency, \DateTimeInterface $date = null)
+ private static function detectPeriodicity(string $baseCurrency, ?\DateTimeInterface $date = null)
{
return array_reduce(
@@ -115,7 +115,7 @@ static function ($periodicity, $entry) use ($date) {
*
* @return bool
*/
- private static function supportQuoteCurrency(string $quoteCurrency, \DateTimeInterface $date = null): bool
+ private static function supportQuoteCurrency(string $quoteCurrency, ?\DateTimeInterface $date = null): bool
{
if ($date) {
$date = $date->format('Y-m-d');
@@ -167,7 +167,7 @@ public function getName(): string
* @throws UnsupportedDateException
* @throws UnsupportedExchangeQueryException
*/
- private function doCreateRate(ExchangeRateQuery $exchangeQuery, \DateTimeInterface $requestedDate = null): ExchangeRate
+ private function doCreateRate(ExchangeRateQuery $exchangeQuery, ?\DateTimeInterface $requestedDate = null): ExchangeRate
{
$currencyPair = $exchangeQuery->getCurrencyPair();
$baseCurrency = $currencyPair->getBaseCurrency();
@@ -224,7 +224,7 @@ private function doCreateRate(ExchangeRateQuery $exchangeQuery, \DateTimeInterfa
*
* @return string
*/
- private function buildUrl(string $baseCurrency, \DateTimeInterface $requestedDate = null): string
+ private function buildUrl(string $baseCurrency, ?\DateTimeInterface $requestedDate = null): string
{
$data = isset($requestedDate) ? ['ondate' => $requestedDate->format('Y-m-d')] : [];
$data += ['periodicity' => (int) self::detectPeriodicity($baseCurrency, $requestedDate)];
diff --git a/src/Service/Registry.php b/src/Service/Registry.php
index 57545db..c1d2a00 100644
--- a/src/Service/Registry.php
+++ b/src/Service/Registry.php
@@ -55,7 +55,9 @@ public static function getServices(): array
'exchangeratehost' => ExchangerateHost::class,
'apilayer_fixer' => ApiLayer\Fixer::class,
'apilayer_currency_data' => ApiLayer\CurrencyData::class,
- 'apilayer_exchange_rates_data' => ApiLayer\ExchangeRatesData::class
+ 'apilayer_exchange_rates_data' => ApiLayer\ExchangeRatesData::class,
+ 'frankfurter' => Frankfurter::class,
+ 'fawazahmed_currency_api' => FawazahmedCurrencyAPI::class,
];
}
}
diff --git a/tests/Fixtures/Service/FawazahmedCurrencyAPI/EUR_historic.json b/tests/Fixtures/Service/FawazahmedCurrencyAPI/EUR_historic.json
new file mode 100644
index 0000000..a81e118
--- /dev/null
+++ b/tests/Fixtures/Service/FawazahmedCurrencyAPI/EUR_historic.json
@@ -0,0 +1,345 @@
+{
+ "date": "2025-01-01",
+ "eur": {
+ "1inch": 2.71598641,
+ "aave": 0.0033322194,
+ "ada": 1.20869787,
+ "aed": 3.80268723,
+ "afn": 72.94742063,
+ "agix": 1.90379556,
+ "akt": 0.36034647,
+ "algo": 3.08213048,
+ "all": 96.18797548,
+ "amd": 410.37202845,
+ "amp": 140.63087847,
+ "ang": 1.86146679,
+ "aoa": 953.57786559,
+ "ape": 0.85912957,
+ "apt": 0.11958286,
+ "ar": 0.06427208,
+ "arb": 1.43701846,
+ "ars": 1067.49150988,
+ "atom": 0.16722,
+ "ats": 13.7603,
+ "aud": 1.67327603,
+ "avax": 0.02904335,
+ "awg": 1.85345409,
+ "axs": 0.16561449,
+ "azm": 8801.28261814,
+ "azn": 1.76025652,
+ "bake": 4.16643653,
+ "bam": 1.95583,
+ "bat": 4.45300015,
+ "bbd": 2.07089843,
+ "bch": 0.0023707182,
+ "bdt": 123.7482128,
+ "bef": 40.3399,
+ "bgn": 1.95583,
+ "bhd": 0.3893289,
+ "bif": 3060.51104329,
+ "bmd": 1.03544921,
+ "bnb": 0.0014674881,
+ "bnd": 1.41419799,
+ "bob": 7.16791742,
+ "brl": 6.40363794,
+ "bsd": 1.03544921,
+ "bsv": 0.020470766,
+ "bsw": 13.65965625,
+ "btc": 0.000011030226,
+ "btcb": 1.5999423,
+ "btg": 0.11154424,
+ "btn": 88.61022344,
+ "btt": 970097.79949998,
+ "busd": 1.03904127,
+ "bwp": 14.50094576,
+ "byn": 3.38596108,
+ "byr": 33859.61081591,
+ "bzd": 2.08628058,
+ "cad": 1.48965371,
+ "cake": 0.41085843,
+ "cdf": 2957.75616824,
+ "celo": 1.61170443,
+ "cfx": 6.57244922,
+ "chf": 0.93997134,
+ "chz": 12.60934255,
+ "clp": 1029.65823499,
+ "cnh": 7.59554477,
+ "cny": 7.55841739,
+ "comp": 0.014151956,
+ "cop": 4563.76538552,
+ "crc": 525.59470546,
+ "cro": 7.36369666,
+ "crv": 1.15186184,
+ "cspr": 69.20359009,
+ "cuc": 1.03544921,
+ "cup": 24.86048283,
+ "cve": 110.27,
+ "cvx": 0.22968469,
+ "cyp": 0.585274,
+ "czk": 25.2021966,
+ "dai": 1.04083878,
+ "dash": 0.027221791,
+ "dcr": 0.067733563,
+ "dem": 1.95583,
+ "dfi": 57.64361347,
+ "djf": 184.25648306,
+ "dkk": 7.45866176,
+ "doge": 3.23604554,
+ "dop": 63.26837675,
+ "dot": 0.15478057,
+ "dydx": 0.72316359,
+ "dzd": 140.62465872,
+ "eek": 15.64664,
+ "egld": 0.030916744,
+ "egp": 52.6406433,
+ "enj": 4.94444186,
+ "eos": 1.3382439,
+ "ern": 15.53173819,
+ "esp": 166.38600001,
+ "etb": 132.5222401,
+ "etc": 0.041327316,
+ "eth": 0.00030957446,
+ "eur": 1,
+ "fei": 1.04560989,
+ "fil": 0.20824284,
+ "fim": 5.94573,
+ "fjd": 2.41127663,
+ "fkp": 0.82741463,
+ "flow": 1.48640602,
+ "flr": 39.5743945,
+ "frax": 1.04606708,
+ "frf": 6.55957,
+ "ftm": 1.54524146,
+ "ftt": 0.27742022,
+ "fxs": 0.29560771,
+ "gala": 30.02398614,
+ "gbp": 0.82741463,
+ "gel": 2.91315058,
+ "ggp": 0.82741463,
+ "ghc": 152201.91209084,
+ "ghs": 15.22019121,
+ "gip": 0.82741463,
+ "gmd": 74.92828721,
+ "gmx": 0.038214266,
+ "gnf": 8953.73693022,
+ "gno": 0.0039157843,
+ "grd": 340.75000001,
+ "grt": 5.16134878,
+ "gt": 0.062359078,
+ "gtq": 7.9886887,
+ "gusd": 1.0422519,
+ "gyd": 216.68678192,
+ "hbar": 3.86349001,
+ "hkd": 8.04436101,
+ "hnl": 26.31722493,
+ "hnt": 0.17221786,
+ "hot": 446.21352233,
+ "hrk": 7.5345,
+ "ht": 0.86749166,
+ "htg": 135.44355327,
+ "huf": 411.61261863,
+ "icp": 0.10438534,
+ "idr": 16844.23085478,
+ "iep": 0.787564,
+ "ils": 3.77033141,
+ "imp": 0.82741463,
+ "imx": 0.7857388,
+ "inj": 0.052882984,
+ "inr": 88.61022344,
+ "iqd": 1356.25857159,
+ "irr": 43538.90136604,
+ "isk": 143.95684876,
+ "itl": 1936.27000007,
+ "jep": 0.82741463,
+ "jmd": 161.74033953,
+ "jod": 0.73413349,
+ "jpy": 162.87028137,
+ "kas": 9.11145184,
+ "kava": 2.31653353,
+ "kcs": 0.099052048,
+ "kda": 1.10871874,
+ "kes": 133.95929494,
+ "kgs": 89.87976701,
+ "khr": 4170.79206077,
+ "klay": 5.18193733,
+ "kmf": 491.96775002,
+ "knc": 1.96956323,
+ "kpw": 931.90482385,
+ "krw": 1530.6142651,
+ "ksm": 0.031321893,
+ "kwd": 0.31924086,
+ "kyd": 0.85640855,
+ "kzt": 543.54045298,
+ "lak": 22650.38591768,
+ "lbp": 92930.04373323,
+ "ldo": 0.59500365,
+ "leo": 0.11461827,
+ "link": 0.051650656,
+ "lkr": 303.55772988,
+ "lrc": 5.33514179,
+ "lrd": 190.44206468,
+ "lsl": 19.56265235,
+ "ltc": 0.0098452516,
+ "ltl": 3.4528,
+ "luf": 40.3399,
+ "luna": 2.45966421,
+ "lunc": 9579.84334338,
+ "lvl": 0.7028,
+ "lyd": 5.09468679,
+ "mad": 10.47675461,
+ "mana": 2.2219518,
+ "matic": 2.29070364,
+ "mbx": 3.00658726,
+ "mdl": 19.07703409,
+ "mga": 4881.38687021,
+ "mgf": 24406.93435104,
+ "mina": 1.80145371,
+ "mkd": 61.24119478,
+ "mkr": 0.00069621295,
+ "mmk": 2173.16161056,
+ "mnt": 3556.44598839,
+ "mop": 8.28569185,
+ "mro": 412.24672544,
+ "mru": 41.22467254,
+ "mtl": 0.4293,
+ "mur": 48.43119738,
+ "mvr": 15.98410085,
+ "mwk": 1797.08704158,
+ "mxn": 21.60470544,
+ "mxv": 2.57672611,
+ "myr": 4.63001954,
+ "mzm": 66157.04831964,
+ "mzn": 66.15704832,
+ "nad": 19.56265235,
+ "near": 0.21074378,
+ "neo": 0.076535361,
+ "nexo": 0.79687867,
+ "nft": 1989467.75247874,
+ "ngn": 1598.66721707,
+ "nio": 38.11660754,
+ "nlg": 2.20371,
+ "nok": 11.78905028,
+ "npr": 141.84281517,
+ "nzd": 1.85057196,
+ "okb": 0.021113232,
+ "omr": 0.39864274,
+ "one": 39.59079541,
+ "op": 0.58740674,
+ "ordi": 0.037983531,
+ "pab": 1.03544921,
+ "paxg": 0.0003952891,
+ "pen": 3.89348875,
+ "pepe": 50923.12242243,
+ "pgk": 4.11702105,
+ "php": 60.14442156,
+ "pkr": 288.2464238,
+ "pln": 4.2787249,
+ "pte": 200.48200001,
+ "pyg": 8089.41125567,
+ "qar": 3.76903513,
+ "qnt": 0.0097355418,
+ "qtum": 0.34423421,
+ "rol": 49772.3016968,
+ "ron": 4.97723017,
+ "rpl": 0.091284323,
+ "rsd": 117.08779087,
+ "rub": 117.67608921,
+ "rune": 0.2310748,
+ "rvn": 51.46553539,
+ "rwf": 1441.27647197,
+ "sand": 1.90132725,
+ "sar": 3.88293455,
+ "sbd": 8.70441866,
+ "scr": 14.74901674,
+ "sdd": 62272.45852812,
+ "sdg": 622.72458528,
+ "sek": 11.46024175,
+ "sgd": 1.41419799,
+ "shib": 48834.20925728,
+ "shp": 0.82741463,
+ "sit": 239.64000001,
+ "skk": 30.126,
+ "sle": 23.55768653,
+ "sll": 23557.68653065,
+ "snx": 0.53828059,
+ "sol": 0.0054542573,
+ "sos": 591.47649231,
+ "spl": 0.17257487,
+ "srd": 36.77478675,
+ "srg": 36774.7867507,
+ "std": 24472.72422169,
+ "stn": 24.47272422,
+ "stx": 0.67712282,
+ "sui": 0.25059727,
+ "svc": 9.06018061,
+ "syp": 13462.85836664,
+ "szl": 19.56265235,
+ "thb": 35.55558909,
+ "theta": 0.46789675,
+ "tjs": 11.28697599,
+ "tmm": 18169.17404607,
+ "tmt": 3.63383481,
+ "tnd": 3.30361213,
+ "ton": 0.18806223,
+ "top": 2.4798149,
+ "trl": 36611377.62108472,
+ "trx": 4.08134793,
+ "try": 36.61137762,
+ "ttd": 7.03869007,
+ "tusd": 1.04259804,
+ "tvd": 1.67327603,
+ "twd": 33.91826771,
+ "twt": 0.85746833,
+ "tzs": 2486.96168439,
+ "uah": 43.03849544,
+ "ugx": 3809.59627539,
+ "uni": 0.077949917,
+ "usd": 1.03544921,
+ "usdc": 1.04044305,
+ "usdd": 1.04405991,
+ "usdp": 1.0404469,
+ "usdt": 1.04250322,
+ "uyu": 45.24375389,
+ "uzs": 13304.08296672,
+ "val": 1936.27000007,
+ "veb": 5380358901.304507,
+ "ved": 53.80443098,
+ "vef": 5380443.09817346,
+ "ves": 53.80443098,
+ "vet": 23.8120514,
+ "vnd": 26384.70200176,
+ "vuv": 125.23926962,
+ "waves": 0.68794034,
+ "wemix": 1.36774699,
+ "woo": 4.99456078,
+ "wst": 2.88537727,
+ "xaf": 655.95700002,
+ "xag": 0.035834892,
+ "xau": 0.00039451278,
+ "xaut": 0.00039635431,
+ "xbt": 0.000011030226,
+ "xcd": 2.80222747,
+ "xch": 0.049161368,
+ "xdc": 14.64091191,
+ "xdr": 0.79397697,
+ "xec": 31065.81853661,
+ "xem": 43.84043787,
+ "xlm": 3.04984607,
+ "xmr": 0.0053429279,
+ "xof": 655.95700002,
+ "xpd": 0.0011341174,
+ "xpf": 119.33174225,
+ "xpt": 0.0011416798,
+ "xrp": 0.49354346,
+ "xtz": 0.80947926,
+ "yer": 258.54071693,
+ "zar": 19.56265235,
+ "zec": 0.018528511,
+ "zil": 51.42870382,
+ "zmk": 28996.55354632,
+ "zmw": 28.99655355,
+ "zwd": 374.72907013,
+ "zwg": 26.37806761,
+ "zwl": 65911.51589291
+ }
+}
diff --git a/tests/Fixtures/Service/FawazahmedCurrencyAPI/EUR_latest.json b/tests/Fixtures/Service/FawazahmedCurrencyAPI/EUR_latest.json
new file mode 100644
index 0000000..dd3b872
--- /dev/null
+++ b/tests/Fixtures/Service/FawazahmedCurrencyAPI/EUR_latest.json
@@ -0,0 +1 @@
+{"date":"2025-09-04","eur":{"1inch":4.70715339,"aave":0.0035704213,"ada":1.36776967,"aed":4.28288844,"afn":79.82184467,"agix":4.33238203,"akt":1.03908097,"algo":5.00460458,"all":97.53452106,"amd":443.48172788,"amp":347.30702716,"ang":2.0991116,"aoa":1075.43819703,"ape":2.02315106,"apt":0.26689379,"ar":0.17998704,"arb":2.27868249,"ars":1587.49663237,"atom":0.25821595,"ats":13.7603,"aud":1.78282365,"avax":0.04640828,"awg":2.08750723,"axs":0.48680803,"azm":9912.67692936,"azn":1.98253539,"bake":22.49827307,"bam":1.95583,"bat":7.463448,"bbd":2.33241032,"bch":0.0019822088,"bdt":141.84115428,"bef":40.3399,"bgn":1.95583,"bhd":0.43849314,"bif":3466.65956631,"bmd":1.16620516,"bnb":0.0013639495,"bnd":1.50226499,"bob":8.04817396,"brl":6.35724822,"bsd":1.16620516,"bsv":0.04466083,"bsw":64.13665405,"btc":0.000010414095,"btg":2.2125896,"btn":102.72372531,"btt":1786871.63271172,"busd":1.16620232,"bwp":16.76331193,"byn":3.9351244,"byr":39351.24400277,"bzd":2.34569978,"cad":1.60933667,"cake":0.48781296,"cdf":3390.72968645,"celo":3.89182684,"cfx":6.60494993,"chf":0.93747699,"chz":29.39096726,"clp":1129.20297674,"cnh":8.32706426,"cny":8.32692435,"comp":0.027036573,"cop":4667.38440835,"crc":589.30194574,"cro":4.29377449,"crv":1.47251678,"cspr":120.24000216,"cuc":1.16620516,"cup":27.93848929,"cve":110.27,"cvx":0.31931699,"cyp":0.585274,"czk":24.43125312,"dai":1.16621937,"dash":0.048837478,"dcr":0.070304449,"dem":1.95583,"dfi":647.72145203,"djf":207.57174353,"dkk":7.46462632,"doge":5.31578944,"dop":73.81656494,"dot":0.30199209,"dydx":1.94680706,"dzd":151.47672729,"eek":15.64664,"egld":0.083179688,"egp":56.62461615,"enj":17.14350411,"eos":2.40085557,"ern":17.49307737,"esp":166.386,"etb":166.41598913,"etc":0.056026595,"eth":0.00026098797,"eur":1,"eurc":1.00032112,"fei":1.17283834,"fil":0.50070669,"fim":5.94573,"fjd":2.65173302,"fkp":0.86763819,"flow":2.83068374,"flr":56.79876292,"frax":0.41706219,"frf":6.55957,"ftt":1.46471775,"gala":71.93125587,"gbp":0.86763819,"gel":3.14184992,"ggp":0.86763819,"ghc":140405.14854056,"ghs":14.04051485,"gip":0.86763819,"gmd":83.9373157,"gmx":0.07831839,"gnf":10111.89341743,"gno":0.0087770348,"grd":340.75,"grt":13.03117813,"gt":0.068469677,"gtq":8.93932438,"gusd":1.16620604,"gyd":243.64461159,"hbar":5.34614571,"hkd":9.09383937,"hnl":30.56196278,"hnt":0.46928654,"hot":1237.14711727,"hrk":7.5345,"ht":2.7860467,"htg":152.39955306,"huf":393.5298685,"icp":0.23765389,"idr":19184.95285945,"iep":0.787564,"ils":3.9179183,"imp":0.86763819,"imx":2.22764892,"inj":0.088243774,"inr":102.72372531,"iqd":1527.37141135,"irr":49132.6776587,"isk":143.64181313,"itl":1936.27,"jep":0.86763819,"jmd":186.21373136,"jod":0.82683946,"jpy":172.56754041,"kas":13.87496189,"kava":3.16832562,"kcs":0.076571098,"kda":3.31342543,"kes":150.54657274,"kgs":101.92454842,"khr":4670.89270041,"klay":7.8285773,"kmf":491.96775,"knc":3.10757425,"kpw":1049.58300897,"krw":1622.17633929,"ksm":0.076324748,"kwd":0.35663039,"kyd":0.96682683,"kzt":629.39292541,"lak":25261.10479949,"lbp":104533.77237439,"ldo":0.94202943,"leo":0.12229835,"link":0.048849807,"lkr":351.91427666,"lrc":10.98859012,"lrd":234.39057218,"lsl":20.60264472,"ltc":0.010344288,"ltl":3.4528,"luf":40.3399,"luna":7.88289663,"lunc":19686.01362856,"lvl":0.7028,"lyd":6.31908996,"mad":10.58787421,"mana":3.73909848,"mbx":7.26719474,"mdl":19.45074245,"mga":5143.43199902,"mgf":25717.15999509,"mina":6.47027629,"mkd":61.49637373,"mkr":0.00065043142,"mmk":2448.36900795,"mnt":4193.95255871,"mop":9.36665455,"mro":466.11596384,"mru":46.61159638,"mtl":0.4293,"mur":53.7762933,"mvr":18.0128401,"mwk":2025.67317307,"mxn":21.82595702,"mxv":2.55599275,"myr":4.92424269,"mzm":74509.23118625,"mzn":74.50923119,"nad":20.60264472,"near":0.4727302,"neo":0.17680592,"nexo":0.91699156,"nft":2578035.73534334,"ngn":1785.78540373,"nio":42.90470621,"nlg":2.20371,"nok":11.71755589,"npr":164.43500329,"nzd":1.98239615,"okb":0.0065645025,"omr":0.44881084,"one":113.67964478,"op":1.63092947,"ordi":0.13031244,"pab":1.16620516,"paxg":0.00032719941,"pen":4.11891861,"pepe":117515.34068728,"pgk":4.90860547,"php":66.77731247,"pkr":330.09329965,"pln":4.25344236,"pol":4.18503175,"pte":200.482,"pyg":8413.6531259,"qar":4.24498678,"qnt":0.011495095,"qtum":0.42721523,"rol":50751.20831775,"ron":5.07512083,"rpl":0.17463902,"rsd":117.21588437,"rub":94.45762431,"rune":0.96289668,"rvn":88.91857506,"rwf":1686.87642387,"sand":4.14071297,"sar":4.37326934,"sbd":9.78691572,"scr":16.73182635,"sdd":70026.4725091,"sdg":700.26472509,"sek":10.9896236,"sgd":1.50226499,"shib":93525.09139282,"shp":0.86763819,"sit":239.64,"skk":30.126,"sle":27.15713498,"sll":27157.13498095,"snx":1.69682763,"sol":0.0055300485,"sos":665.85411399,"spl":0.19436753,"srd":45.34629982,"srg":45346.29981922,"std":24748.54729645,"stn":24.7485473,"stx":1.83079478,"sui":0.34522222,"svc":10.20429514,"syp":15162.8269093,"szl":20.60264472,"thb":37.6492654,"theta":1.47287471,"tjs":10.97182066,"tmm":20386.71980545,"tmt":4.07734396,"tnd":3.39892259,"ton":0.36491786,"top":2.78711924,"trl":48018355.26003679,"trx":3.41502259,"try":48.01835526,"ttd":7.90533854,"tusd":1.16958778,"tvd":1.78282365,"twd":35.75889087,"twt":1.58233452,"tzs":2911.42468546,"uah":48.26986944,"ugx":4111.86994761,"uni":0.12048971,"usd":1.16620516,"usdc":1.15993722,"usdd":1.1658378,"usdp":1.16445847,"usdt":1.16575292,"uyu":46.61004151,"uzs":14461.82411076,"val":1936.27,"veb":17599770834.04549,"ved":176.31678439,"vef":17631678.4389903,"ves":176.31678439,"vet":48.68468612,"vnd":30873.51341166,"vuv":139.99238454,"waves":1.02554739,"wemix":1.63537993,"woo":17.02852499,"wst":3.22320231,"xaf":655.957,"xag":0.028470389,"xau":0.00032869539,"xaut":0.00032836938,"xbt":0.000010414095,"xcd":3.15629813,"xcg":2.0991116,"xch":0.12379621,"xdc":14.87605467,"xdr":0.85366807,"xec":59685.68148141,"xem":488.21235022,"xlm":3.22919827,"xmr":0.0043535538,"xof":655.957,"xpd":0.0010221993,"xpf":119.33174224,"xpt":0.00081982567,"xrp":0.41050871,"xtz":1.59964242,"yer":280.06740698,"zar":20.60264472,"zec":0.028278365,"zil":101.81175172,"zmk":27803.26762861,"zmw":27.80326763,"zwd":422.04964679,"zwg":31.23919681,"zwl":78058.13705746}}
diff --git a/tests/Fixtures/Service/Frankfurter/EUR_USD.json b/tests/Fixtures/Service/Frankfurter/EUR_USD.json
new file mode 100644
index 0000000..e556624
--- /dev/null
+++ b/tests/Fixtures/Service/Frankfurter/EUR_USD.json
@@ -0,0 +1,8 @@
+{
+ "amount": 1,
+ "base": "EUR",
+ "date": "2025-09-05",
+ "rates": {
+ "USD": 1.1697
+ }
+}
diff --git a/tests/Fixtures/Service/Frankfurter/EUR_USD_historic.json b/tests/Fixtures/Service/Frankfurter/EUR_USD_historic.json
new file mode 100644
index 0000000..8c127ba
--- /dev/null
+++ b/tests/Fixtures/Service/Frankfurter/EUR_USD_historic.json
@@ -0,0 +1,8 @@
+{
+ "amount": 1,
+ "base": "EUR",
+ "date": "1999-04-13",
+ "rates": {
+ "USD": 1.0765
+ }
+}
diff --git a/tests/Tests/CurrencyPairTest.php b/tests/Tests/CurrencyPairTest.php
index 58e5c26..95ee8c1 100644
--- a/tests/Tests/CurrencyPairTest.php
+++ b/tests/Tests/CurrencyPairTest.php
@@ -29,7 +29,7 @@ public function it_creates_a_pair_from_a_valid_string($string, $baseCurrency, $q
$this->assertEquals($quoteCurrency, $pair->getQuoteCurrency());
}
- public function validStringProvider()
+ public static function validStringProvider()
{
return [
['EUR/USD', 'EUR', 'USD'],
@@ -49,7 +49,7 @@ public function it_throws_an_exception_when_creating_from_an_invalid_string($str
CurrencyPair::createFromString($string);
}
- public function invalidStringProvider()
+ public static function invalidStringProvider()
{
return [
['EUR'], ['EUR/'], ['EU/US'], ['EUR/US'], ['US/EUR'], ['00'], ['00/'], ['007/00'],
diff --git a/tests/Tests/Service/ApiLayer/ExchangeRatesDataTest.php b/tests/Tests/Service/ApiLayer/ExchangeRatesDataTest.php
index 5b24461..599ff86 100644
--- a/tests/Tests/Service/ApiLayer/ExchangeRatesDataTest.php
+++ b/tests/Tests/Service/ApiLayer/ExchangeRatesDataTest.php
@@ -82,7 +82,7 @@ public function it_throws_An_unsupported_currency_pair_exception(
$service->getExchangeRate($query);
}
- public function unsupportedCurrencyPairResponsesProvider(): array
+ public static function unsupportedCurrencyPairResponsesProvider(): array
{
$dir = __DIR__.'/../../../Fixtures/Service/ApiLayer/ExchangeRatesData/';
diff --git a/tests/Tests/Service/CentralBankOfCzechRepublicTest.php b/tests/Tests/Service/CentralBankOfCzechRepublicTest.php
index 87bdea9..4021a09 100644
--- a/tests/Tests/Service/CentralBankOfCzechRepublicTest.php
+++ b/tests/Tests/Service/CentralBankOfCzechRepublicTest.php
@@ -111,7 +111,7 @@ public function it_fetches_idr_rate()
$pair = CurrencyPair::createFromString('IDR/CZK');
$rate = $this->createService()->getExchangeRate(new ExchangeRateQuery($pair));
- $this->assertSame(0.001798, $rate->getValue());
+ $this->assertSame(0.001798, (float)\number_format($rate->getValue(), 6));
$this->assertEquals('central_bank_of_czech_republic', $rate->getProviderName());
$this->assertSame($pair, $rate->getCurrencyPair());
}
diff --git a/tests/Tests/Service/CurrencyLayerTest.php b/tests/Tests/Service/CurrencyLayerTest.php
index 4a442b3..0d4b459 100644
--- a/tests/Tests/Service/CurrencyLayerTest.php
+++ b/tests/Tests/Service/CurrencyLayerTest.php
@@ -106,7 +106,7 @@ public function it_fetches_a_historical_rate_normal_mode()
$content = file_get_contents(__DIR__.'/../../Fixtures/Service/CurrencyLayer/historical_success.json');
$date = new \DateTime('2015-05-06');
$expectedDate = new \DateTime();
- $expectedDate->setTimestamp(1430870399);
+ $expectedDate->setTimestamp(1430784000);
$service = new CurrencyLayer($this->getHttpAdapterMock($uri, $content), null, ['access_key' => 'secret']);
$rate = $service->getExchangeRate(new HistoricalExchangeRateQuery($pair, $date));
@@ -126,7 +126,7 @@ public function it_fetches_a_historical_rate_enterprise_mode()
$content = file_get_contents(__DIR__.'/../../Fixtures/Service/CurrencyLayer/historical_success.json');
$date = new \DateTime('2015-05-06');
$expectedDate = new \DateTime();
- $expectedDate->setTimestamp(1430870399);
+ $expectedDate->setTimestamp(1430784000);
$pair = CurrencyPair::createFromString('USD/AED');
$service = new CurrencyLayer($this->getHttpAdapterMock($uri, $content), null, ['access_key' => 'secret', 'enterprise' => true]);
diff --git a/tests/Tests/Service/ExchangeRatesApiTest.php b/tests/Tests/Service/ExchangeRatesApiTest.php
index 83fd0a1..b81d03a 100644
--- a/tests/Tests/Service/ExchangeRatesApiTest.php
+++ b/tests/Tests/Service/ExchangeRatesApiTest.php
@@ -91,7 +91,7 @@ public function it_throws_An_unsupported_currency_pair_exception(
$service->getExchangeRate($query);
}
- public function unsupportedCurrencyPairResponsesProvider(): array
+ public static function unsupportedCurrencyPairResponsesProvider(): array
{
$dir = __DIR__.'/../../Fixtures/Service/ExchangeRatesApi/';
diff --git a/tests/Tests/Service/FawazahmedCurrencyAPITest.php b/tests/Tests/Service/FawazahmedCurrencyAPITest.php
new file mode 100644
index 0000000..a0e11f2
--- /dev/null
+++ b/tests/Tests/Service/FawazahmedCurrencyAPITest.php
@@ -0,0 +1,49 @@
+createMock('Http\Client\HttpClient'));
+
+ $this->assertSame('fawazahmed_currency_api', $service->getName());
+ }
+
+ public function testGetLatestExchangeRate(): void
+ {
+ $url = 'https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/eur.min.json';
+ $content = file_get_contents(__DIR__.'/../../Fixtures/Service/FawazahmedCurrencyAPI/EUR_latest.json');
+
+ $pair = CurrencyPair::createFromString('EUR/USD');
+ $service = new FawazahmedCurrencyAPI($this->getHttpAdapterMock($url, $content));
+
+ $rate = $service->getExchangeRate(new ExchangeRateQuery($pair));
+
+ $this->assertSame(1.16620516, $rate->getValue());
+ $this->assertEquals(new \DateTime('2025-09-04'), $rate->getDate());
+ }
+
+ public function testGetHistoricExchangeRate(): void
+ {
+ $url = 'https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@2025-01-01/v1/currencies/eur.min.json';
+ $content = file_get_contents(__DIR__.'/../../Fixtures/Service/FawazahmedCurrencyAPI/EUR_historic.json');
+
+ $pair = CurrencyPair::createFromString('EUR/USD');
+ $service = new FawazahmedCurrencyAPI($this->getHttpAdapterMock($url, $content));
+
+ $rate = $service->getExchangeRate(new HistoricalExchangeRateQuery($pair, new \DateTime('2025-01-01')));
+
+ $this->assertSame(1.03544921, $rate->getValue());
+ $this->assertEquals(new \DateTime('2025-01-01'), $rate->getDate());
+ }
+}
diff --git a/tests/Tests/Service/FrankfurterTest.php b/tests/Tests/Service/FrankfurterTest.php
new file mode 100644
index 0000000..75e2898
--- /dev/null
+++ b/tests/Tests/Service/FrankfurterTest.php
@@ -0,0 +1,46 @@
+createMock('Http\Client\HttpClient'));
+
+ $this->assertSame('frankfurter', $service->getName());
+ }
+
+ public function testFetchLatestRate(): void
+ {
+ $url = 'https://api.frankfurter.dev/v1/latest?base=EUR&symbols=USD';
+ $content = file_get_contents(__DIR__.'/../../Fixtures/Service/Frankfurter/EUR_USD.json');
+
+ $pair = CurrencyPair::createFromString('EUR/USD');
+ $service = new Frankfurter($this->getHttpAdapterMock($url, $content));
+
+ $rate = $service->getExchangeRate(new ExchangeRateQuery($pair));
+
+ $this->assertSame(1.1697, $rate->getValue());
+ $this->assertEquals(new \DateTime('2025-09-05'), $rate->getDate());
+ }
+
+ public function testFetchHistoricRate(): void
+ {
+ $url = 'https://api.frankfurter.dev/v1/1999-04-13?base=EUR&symbols=USD';
+ $content = file_get_contents(__DIR__.'/../../Fixtures/Service/Frankfurter/EUR_USD_historic.json');
+
+ $pair = CurrencyPair::createFromString('EUR/USD');
+ $service = new Frankfurter($this->getHttpAdapterMock($url, $content));
+
+ $rate = $service->getExchangeRate(new HistoricalExchangeRateQuery($pair, new \DateTime("1999-04-13")));
+
+ $this->assertSame(1.0765, $rate->getValue());
+ $this->assertEquals(new \DateTime('1999-04-13'), $rate->getDate());
+ }
+}
diff --git a/tests/Tests/Service/HttpServiceTest.php b/tests/Tests/Service/HttpServiceTest.php
index b557756..6a693f2 100644
--- a/tests/Tests/Service/HttpServiceTest.php
+++ b/tests/Tests/Service/HttpServiceTest.php
@@ -50,8 +50,8 @@ public function initialize_with_httplug_client()
*/
public function initialize_with_null_as_client()
{
- $this->expectException(\Http\Discovery\Exception\NotFoundException::class);
- $this->expectExceptionMessage('No HTTPlug clients found. Make sure to install a package providing "php-http/client-implementation"');
+ $this->expectNotToPerformAssertions();
+ // if null is passed a new instance HttpClient is generated in the HttpService.
$this->createAnonymousClass(null);
}
diff --git a/tests/Tests/Service/NationalBankOfRomaniaTest.php b/tests/Tests/Service/NationalBankOfRomaniaTest.php
index 31a1a24..8abb1db 100644
--- a/tests/Tests/Service/NationalBankOfRomaniaTest.php
+++ b/tests/Tests/Service/NationalBankOfRomaniaTest.php
@@ -156,7 +156,7 @@ public function it_has_a_name(): void
$this->assertSame('national_bank_of_romania', $service->getName());
}
- public function getSupportedCurrencies(): array
+ public static function getSupportedCurrencies(): array
{
return [
['AED'],