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 -[![Build status](http://img.shields.io/travis/florianv/exchanger/master.svg?style=flat-square)](https://travis-ci.org/florianv/exchanger) -[![Total Downloads](https://img.shields.io/packagist/dt/florianv/exchanger.svg?style=flat-square)](https://packagist.org/packages/florianv/exchanger) -[![Version](http://img.shields.io/packagist/v/florianv/exchanger.svg?style=flat-square)](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'],