Skip to content

Commit

Permalink
Merge pull request #148 from ash-jc-allen/fix/exchange-rate-host
Browse files Browse the repository at this point in the history
Update exchangerate.host driver to match new API
  • Loading branch information
ash-jc-allen authored Oct 29, 2023
2 parents 30a3f5f + 2b262a8 commit 7cc7334
Show file tree
Hide file tree
Showing 11 changed files with 794 additions and 297 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ If you're using the free tier of `https://exchangeratesapi.io` or `https://excha

#### Available Currencies

To get the available currencies supported by the API, you can use the `currencies` mehtod like so:
To get the available currencies supported by the API, you can use the `currencies` method like so:

```php
use AshAllenDesign\LaravelExchangeRates\Classes\ExchangeRate;
Expand Down
150 changes: 143 additions & 7 deletions src/Drivers/ExchangeRateHost/ExchangeRateHostDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@
use AshAllenDesign\LaravelExchangeRates\Drivers\Support\SharedDriverLogicHandler;
use AshAllenDesign\LaravelExchangeRates\Interfaces\ExchangeRateDriver;
use Carbon\Carbon;
use Illuminate\Http\Client\RequestException;

/**
* @see https://exchangerate.host
*/
class ExchangeRateHostDriver implements ExchangeRateDriver
{
private CacheRepository $cacheRepository;

private SharedDriverLogicHandler $sharedDriverLogicHandler;

public function __construct(RequestBuilder $requestBuilder = null, CacheRepository $cacheRepository = null)
{
$requestBuilder = $requestBuilder ?? new RequestBuilder();
$cacheRepository = $cacheRepository ?? new CacheRepository();
$this->cacheRepository = $cacheRepository ?? new CacheRepository();

$this->sharedDriverLogicHandler = new SharedDriverLogicHandler(
$requestBuilder,
$cacheRepository
$this->cacheRepository,
);
}

Expand All @@ -32,15 +35,68 @@ public function __construct(RequestBuilder $requestBuilder = null, CacheReposito
*/
public function currencies(): array
{
return $this->sharedDriverLogicHandler->currencies();
$cacheKey = 'currencies';

if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) {
return $cachedExchangeRate;
}

$response = $this->sharedDriverLogicHandler
->getRequestBuilder()
->makeRequest('/list');

$currencies = array_keys($response->get('currencies'));

$this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $currencies);

return $currencies;
}

/**
* @inheritDoc
*/
public function exchangeRate(string $from, array|string $to, Carbon $date = null): float|array
{
return $this->sharedDriverLogicHandler->exchangeRate($from, $to, $date);
$this->sharedDriverLogicHandler->validateExchangeRateInput($from, $to, $date);

if ($from === $to) {
return 1.0;
}

$cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date ?? Carbon::now());

if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) {
// If the exchange rate has been retrieved from the cache as a
// string (e.g. "1.23"), then cast it to a float (e.g. 1.23).
// If we have retrieved the rates for many currencies, it
// will be an array of floats, so just return it.
return is_string($cachedExchangeRate)
? (float) $cachedExchangeRate
: $cachedExchangeRate;
}

$symbols = is_string($to) ? $to : implode(',', $to);
$queryParams = ['source' => $from, 'currencies' => $symbols];

if ($date) {
$queryParams['date'] = $date->format('Y-m-d');
}

$url = $date ? '/historical' : '/live';

/** @var array<string,float> $response */
$response = $this->sharedDriverLogicHandler
->getRequestBuilder()
->makeRequest($url, $queryParams)
->rates();

$exchangeRate = is_string($to)
? $response[$from.$to]
: $this->removeSourceCurrencyFromKeys($response);

$this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $exchangeRate);

return $exchangeRate;
}

/**
Expand All @@ -52,15 +108,33 @@ public function exchangeRateBetweenDateRange(
Carbon $date,
Carbon $endDate
): array {
return $this->sharedDriverLogicHandler->exchangeRateBetweenDateRange($from, $to, $date, $endDate);
$this->sharedDriverLogicHandler->validateExchangeRateBetweenDateRangeInput($from, $to, $date, $endDate);

$cacheKey = $this->cacheRepository->buildCacheKey($from, $to, $date, $endDate);

if ($cachedExchangeRate = $this->sharedDriverLogicHandler->attemptToResolveFromCache($cacheKey)) {
return $cachedExchangeRate;
}

$conversions = $from === $to
? $this->sharedDriverLogicHandler->exchangeRateDateRangeResultWithSameCurrency($date, $endDate)
: $this->makeRequestForExchangeRates($from, $to, $date, $endDate);

$this->sharedDriverLogicHandler->attemptToStoreInCache($cacheKey, $conversions);

return $conversions;
}

/**
* @inheritDoc
*/
public function convert(int $value, string $from, array|string $to, Carbon $date = null): float|array
{
return $this->sharedDriverLogicHandler->convert($value, $from, $to, $date);
return $this->sharedDriverLogicHandler->convertUsingRates(
$this->exchangeRate($from, $to, $date),
$to,
$value,
);
}

/**
Expand All @@ -73,7 +147,11 @@ public function convertBetweenDateRange(
Carbon $date,
Carbon $endDate
): array {
return $this->sharedDriverLogicHandler->convertBetweenDateRange($value, $from, $to, $date, $endDate);
return $this->sharedDriverLogicHandler->convertUsingRatesForDateRange(
$this->exchangeRateBetweenDateRange($from, $to, $date, $endDate),
$to,
$value,
);
}

/**
Expand All @@ -95,4 +173,62 @@ public function shouldBustCache(bool $bustCache = true): ExchangeRateDriver

return $this;
}

/**
* Make a request to the exchange rates API to get the exchange rates between a
* date range. If only one currency is being used, we flatten the array to
* remove currency symbol before returning it.
*
* @param string $from
* @param string|string[] $to
* @param Carbon $date
* @param Carbon $endDate
* @return array<string, float>|array<string, array<string, float>>
*
* @throws RequestException
*/
private function makeRequestForExchangeRates(string $from, string|array $to, Carbon $date, Carbon $endDate): array
{
$symbols = is_string($to) ? $to : implode(',', $to);

$result = $this->sharedDriverLogicHandler
->getRequestBuilder()
->makeRequest('/timeframe', [
'source' => $from,
'start_date' => $date->format('Y-m-d'),
'end_date' => $endDate->format('Y-m-d'),
'currencies' => $symbols,
]);

$conversions = $result->rates();

foreach ($conversions as $rateDate => $rates) {
$ratesForDay = is_string($to)
? $rates[$from.$to]
: $this->removeSourceCurrencyFromKeys($rates);

$conversions[$rateDate] = $ratesForDay;
}

ksort($conversions);

return $conversions;
}

/**
* The quotes are returned in the format of "USDEUR": 0.1234. We only want the
* converted currency's code (e.g. EUR), so we need to remove the source
* currency from the start of the key (e.g. USD). We can do this by
* removing the first three characters from the key. Strip these
* characters from all the rates and then return the array.
*
* @param array<string,float> $rates
* @return array<string,float>
*/
private function removeSourceCurrencyFromKeys(array $rates): array
{
return collect($rates)
->mapWithKeys(static fn (float $value, string $key): array => [substr($key, 3) => $value])
->all();
}
}
12 changes: 11 additions & 1 deletion src/Drivers/ExchangeRateHost/RequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ class RequestBuilder implements RequestSender
{
private const BASE_URL = 'api.exchangerate.host/';

private string $apiKey;

public function __construct()
{
$this->apiKey = config('laravel-exchange-rates.api_key');
}

/**
* Make an API request to the ExchangeRatesAPI.
*
Expand All @@ -25,7 +32,10 @@ public function makeRequest(string $path, array $queryParams = []): ResponseCont
$protocol = config('laravel-exchange-rates.https') ? 'https://' : 'http://';

$rawResponse = Http::baseUrl($protocol.self::BASE_URL)
->get($path, $queryParams)
->get(
$path,
array_merge(['access_key' => $this->apiKey], $queryParams)
)
->throw()
->json();

Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/ExchangeRateHost/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function get(string $key): mixed

public function rates(): array
{
return $this->get('rates');
return $this->get('quotes');
}

public function raw(): mixed
Expand Down
Loading

0 comments on commit 7cc7334

Please sign in to comment.