Skip to content

Commit f17a67c

Browse files
committed
Add ExchangeRatesApi Driver
1 parent 4d8576b commit f17a67c

File tree

3 files changed

+233
-1
lines changed

3 files changed

+233
-1
lines changed

src/DriverFactory.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Otherguy\Currency\Drivers\FixerIo;
88
use Otherguy\Currency\Drivers\MockCurrencyDriver;
99
use Otherguy\Currency\Drivers\OpenExchangeRates;
10+
use Otherguy\Currency\Drivers\ExchangeRatesApi;
1011
use Otherguy\Currency\Exceptions\DriverNotFoundException;
1112

1213
/**
@@ -21,7 +22,6 @@ class DriverFactory
2122
'fixerio' => FixerIo::class,
2223
'currencylayer' => CurrencyLayer::class,
2324
'openexchangerates' => OpenExchangeRates::class,
24-
'ratesapi' => RatesApi::class,
2525
'exchangeratesapi' => ExchangeRatesApi::class,
2626
];
2727

src/Drivers/ExchangeRatesApi.php

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php namespace Otherguy\Currency\Drivers;
2+
3+
use DateTime;
4+
use Otherguy\Currency\Exceptions\ApiException;
5+
use Otherguy\Currency\Exceptions\CurrencyException;
6+
use Otherguy\Currency\Results\ConversionResult;
7+
use Otherguy\Currency\Symbol;
8+
9+
/**
10+
* Class RatesApi
11+
*
12+
* @package Otherguy\Currency\Drivers
13+
*/
14+
class ExchangeRatesApi extends BaseCurrencyDriver implements CurrencyDriverContract
15+
{
16+
protected $protocol = 'https';
17+
protected $apiURL = 'api.exchangeratesapi.io';
18+
19+
/** @var string $baseCurrency Exchange Rates API default base currency is 'EUR' */
20+
protected $baseCurrency = Symbol::EUR;
21+
22+
/**
23+
* Sets the API key to use. ExchangeRatesAPI has no API keys and is open to use.
24+
*
25+
* @param string $accessKey Your API key.
26+
*
27+
* @return CurrencyDriverContract
28+
* @throws ApiException
29+
*/
30+
public function accessKey(string $accessKey): CurrencyDriverContract
31+
{
32+
throw new ApiException('No Access Key is required for this driver!', 400);
33+
}
34+
35+
/**
36+
* @param string|array $forCurrency
37+
*
38+
* @return ConversionResult
39+
*
40+
* @throws CurrencyException
41+
*/
42+
public function get($forCurrency = []): ConversionResult
43+
{
44+
if (!empty((array)$forCurrency)) {
45+
$this->currencies((array)$forCurrency);
46+
}
47+
48+
// Get API response
49+
$response = $this->apiRequest('latest', [
50+
'base' => $this->getBaseCurrency(),
51+
'symbols' => join(',', $this->getSymbols()),
52+
]);
53+
54+
return new ConversionResult($response['base'], $response['date'], $response['rates']);
55+
}
56+
57+
/**
58+
* @param int|string|DateTime $date
59+
* @param string|array $forCurrency
60+
*
61+
* @return ConversionResult
62+
*
63+
* @throws CurrencyException
64+
*/
65+
public function historical($date = null, $forCurrency = []): ConversionResult
66+
{
67+
// Set date
68+
$this->date($date);
69+
70+
if (!empty((array)$forCurrency)) {
71+
$this->currencies((array)$forCurrency);
72+
}
73+
74+
if (null === $this->getDate()) {
75+
throw new ApiException('Date needs to be set!');
76+
}
77+
78+
// Get API response
79+
$response = $this->apiRequest($this->getDate(), [
80+
'base' => $this->getBaseCurrency(),
81+
'symbols' => join(',', $this->getSymbols()),
82+
]);
83+
84+
return new ConversionResult($response['base'], $response['date'], $response['rates']);
85+
}
86+
87+
/**
88+
* Converts any amount in a given currency to another currency.
89+
*
90+
* @param float $amount The amount to convert.
91+
* @param string $fromCurrency The base currency.
92+
* @param string $toCurrency The target currency.
93+
* @param int|string|DateTime $date The date to get the conversion rate for.
94+
*
95+
* @return float The conversion result.
96+
*
97+
* @throws ApiException
98+
*/
99+
public function convert(float $amount = null, string $fromCurrency = null, string $toCurrency = null, $date = null): float
100+
{
101+
throw new ApiException("Endpoint 'convert' is not supported for this driver!", 404);
102+
}
103+
104+
/**
105+
* Performs an HTTP request.
106+
*
107+
* @param string $endpoint The API endpoint.
108+
* @param array $params The query parameters for this request.
109+
* @param string $method The HTTP method (defaults to 'GET').
110+
*
111+
* @return array|bool The response as decoded JSON.
112+
*
113+
* @throws ApiException
114+
*/
115+
function apiRequest(string $endpoint, array $params = [], string $method = 'GET')
116+
{
117+
// Perform actual API request.
118+
$response = parent::apiRequest($endpoint, $params, $method);
119+
120+
121+
// Handle response exceptions.
122+
if (isset($response['error'])) {
123+
throw new ApiException((string)$response['error'], 500);
124+
}
125+
126+
return $response;
127+
}
128+
}
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
use GuzzleHttp\Client;
4+
use GuzzleHttp\Handler\MockHandler;
5+
use GuzzleHttp\Psr7\Response;
6+
use Otherguy\Currency\DriverFactory;
7+
use Otherguy\Currency\Drivers\ExchangeRatesApi;
8+
use Otherguy\Currency\Drivers\FixerIo;
9+
use Otherguy\Currency\Exceptions\ApiException;
10+
use Otherguy\Currency\Results\ConversionResult;
11+
use Otherguy\Currency\Symbol;
12+
use PHPUnit\Framework\TestCase;
13+
14+
/**
15+
* RatesApiTest
16+
*/
17+
class ExchangeRatesApiTest extends TestCase
18+
{
19+
/** @var ExchangeRatesApi */
20+
private $exchangeRatesApi;
21+
22+
private $mockHandler;
23+
24+
protected function setUp()
25+
{
26+
$this->mockHandler = new MockHandler();
27+
$this->exchangeRatesApi = DriverFactory::make('exchangeratesapi', new Client(['handler' => $this->mockHandler]));
28+
}
29+
30+
/** @test */
31+
public function fails_to_set_api_key()
32+
{
33+
$this->expectException(ApiException::class);
34+
$this->expectExceptionMessage('No Access Key is required for this driver!');
35+
$this->expectExceptionCode(400);
36+
$this->exchangeRatesApi->accessKey('test-access-key');
37+
}
38+
39+
/** @test */
40+
public function can_get_latest_rates()
41+
{
42+
// Response from https://exchangeratesapi.io
43+
$this->mockHandler->append(new Response(200, [], '{"base":"EUR","rates":{"NOK":9.772,"USD":1.1289,"JPY":122.44},"date":"2019-06-13"}'));
44+
45+
$result = $this->exchangeRatesApi->from(Symbol::EUR)->get([Symbol::NOK, Symbol::JPY, Symbol::USD]);
46+
47+
$this->assertInstanceOf(ConversionResult::class, $result);
48+
49+
$this->assertEquals(Symbol::EUR, $result->getBaseCurrency());
50+
$this->assertEquals('2019-06-13', $result->getDate());
51+
$this->assertEquals(9.772, $result->rate(Symbol::NOK));
52+
$this->assertEquals(1.1289, $result->rate(Symbol::USD));
53+
$this->assertEquals(122.44, $result->rate(Symbol::JPY));
54+
}
55+
56+
57+
/** @test */
58+
public function can_get_historical_rates()
59+
{
60+
// Response from https://exchangeratesapi.io
61+
$this->mockHandler->append(new Response(200, [], '{"base":"GBP","rates":{"NOK":10.088752796,"CAD":1.7366601677,"USD":1.636783369,"JPY":170.6398095762,"EUR":1.1961293255},"date":"2013-12-24"}'));
62+
63+
$result = $this->exchangeRatesApi->from(Symbol::GBP)->historical('2013-12-24', [Symbol::NOK, Symbol::CAD, Symbol::USD, Symbol::JPY, Symbol::EUR]);
64+
65+
$this->assertInstanceOf(ConversionResult::class, $result);
66+
67+
$this->assertEquals(Symbol::GBP, $result->getBaseCurrency());
68+
$this->assertEquals('2013-12-24', $result->getDate());
69+
70+
$this->assertEquals(1.636783369, $result->rate(Symbol::USD));
71+
$this->assertEquals(1.1961293255, $result->rate(Symbol::EUR));
72+
$this->assertEquals(1.7366601677, $result->rate(Symbol::CAD));
73+
$this->assertEquals(10.088752796, $result->rate(Symbol::NOK));
74+
$this->assertEquals(170.6398095762, $result->rate(Symbol::JPY));
75+
}
76+
77+
/** @test */
78+
public function fails_to_get_historical_rates_if_date_not_set()
79+
{
80+
$this->expectException(ApiException::class);
81+
$this->exchangeRatesApi->from(Symbol::USD)->to(Symbol::EUR)->historical();
82+
}
83+
84+
/** @test */
85+
public function fails_to_convert_currency_amounts()
86+
{
87+
$this->expectException(ApiException::class);
88+
$this->expectExceptionMessage("Endpoint 'convert' is not supported for this driver!");
89+
$this->expectExceptionCode(404);
90+
91+
$result = $this->exchangeRatesApi->convert(25, Symbol::GBP, Symbol::JPY, '2018-02-22');
92+
}
93+
94+
/** @test */
95+
public function can_handle_response_failures()
96+
{
97+
// Response from https://exchangeratesapi.io
98+
$this->mockHandler->append(new Response(200, [], '{"error":"Symbols \'USD,CAD,EUR,JPY,NOK,CDP\' are invalid for date 2019-06-14."}'));
99+
100+
$this->expectException(ApiException::class);
101+
$this->expectExceptionCode(500);
102+
$this->exchangeRatesApi->from(Symbol::USD)->to(Symbol::LTL)->get();
103+
}
104+
}

0 commit comments

Comments
 (0)