Skip to content

Commit 327785b

Browse files
committed
feat: new apilayer fixer
1 parent e0a909f commit 327785b

File tree

6 files changed

+338
-0
lines changed

6 files changed

+338
-0
lines changed

src/Service/ApiLayer/Fixer.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Exchanger.
7+
*
8+
* (c) Florian Voutzinos <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Exchanger\Service\ApiLayer;
15+
16+
use Exchanger\Contract\CurrencyPair;
17+
use Exchanger\Contract\ExchangeRateQuery;
18+
use Exchanger\Contract\HistoricalExchangeRateQuery;
19+
use Exchanger\Exception\Exception;
20+
use Exchanger\Exception\UnsupportedCurrencyPairException;
21+
use Exchanger\ExchangeRate;
22+
use Exchanger\Service\HttpService;
23+
use Exchanger\Service\SupportsHistoricalQueries;
24+
use Exchanger\StringUtil;
25+
use Exchanger\Contract\ExchangeRate as ExchangeRateContract;
26+
27+
/**
28+
* Fixer Service (https://apilayer.com/marketplace/fixer-api).
29+
*
30+
* @author Florian Voutzinos <[email protected]>
31+
*/
32+
final class Fixer extends HttpService
33+
{
34+
use SupportsHistoricalQueries;
35+
36+
const ACCESS_KEY_OPTION = 'access_key';
37+
38+
const LATEST_URL = 'https://api.apilayer.com/fixer/latest?base=%s&apikey=%s';
39+
40+
const HISTORICAL_URL = 'https://api.apilayer.com/fixer/%s?base=%s&apikey=%s';
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function processOptions(array &$options): void
46+
{
47+
if (!isset($options[self::ACCESS_KEY_OPTION])) {
48+
throw new \InvalidArgumentException('The "access_key" option must be provided to use https://apilayer.com/marketplace/fixer-api');
49+
}
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
protected function getLatestExchangeRate(ExchangeRateQuery $exchangeQuery): ExchangeRateContract
56+
{
57+
$currencyPair = $exchangeQuery->getCurrencyPair();
58+
59+
$url = sprintf(
60+
self::LATEST_URL,
61+
$exchangeQuery->getCurrencyPair()->getBaseCurrency(),
62+
$this->options[self::ACCESS_KEY_OPTION]
63+
);
64+
65+
return $this->doCreateRate($url, $currencyPair);
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
protected function getHistoricalExchangeRate(HistoricalExchangeRateQuery $exchangeQuery): ExchangeRateContract
72+
{
73+
$currencyPair = $exchangeQuery->getCurrencyPair();
74+
75+
$url = sprintf(
76+
self::HISTORICAL_URL,
77+
$exchangeQuery->getDate()->format('Y-m-d'),
78+
$exchangeQuery->getCurrencyPair()->getBaseCurrency(),
79+
$this->options[self::ACCESS_KEY_OPTION]
80+
);
81+
82+
return $this->doCreateRate($url, $currencyPair);
83+
}
84+
85+
/**
86+
* {@inheritdoc}
87+
*/
88+
public function supportQuery(ExchangeRateQuery $exchangeQuery): bool
89+
{
90+
return true;
91+
}
92+
93+
/**
94+
* Creates a rate.
95+
*
96+
* @param string $url
97+
* @param CurrencyPair $currencyPair
98+
*
99+
* @return ExchangeRate
100+
*
101+
* @throws Exception
102+
*/
103+
private function doCreateRate($url, CurrencyPair $currencyPair): ExchangeRate
104+
{
105+
$content = $this->request($url);
106+
$data = StringUtil::jsonToArray($content);
107+
108+
if (isset($data['error'])) {
109+
throw new Exception($this->getErrorMessage($data['error']['code']));
110+
}
111+
112+
if (isset($data['rates'][$currencyPair->getQuoteCurrency()])) {
113+
$date = new \DateTime($data['date']);
114+
$rate = $data['rates'][$currencyPair->getQuoteCurrency()];
115+
116+
return $this->createRate($currencyPair, (float) $rate, $date);
117+
}
118+
119+
throw new UnsupportedCurrencyPairException($currencyPair, $this);
120+
}
121+
122+
/**
123+
* Gets the error message corresponding to the error code.
124+
*
125+
* @param string $code The error code
126+
*
127+
* @return string
128+
*/
129+
private function getErrorMessage($code): string
130+
{
131+
$errors = [
132+
404 => 'The requested resource does not exist.',
133+
101 => 'No API Key was specified or an invalid API Key was specified.',
134+
103 => 'The requested API endpoint does not exist.',
135+
104 => 'The maximum allowed API amount of monthly API requests has been reached.',
136+
105 => 'The current subscription plan does not support this API endpoint.',
137+
106 => 'The current request did not return any results.',
138+
102 => 'The account this API request is coming from is inactive.',
139+
201 => 'An invalid base currency has been entered.',
140+
202 => 'One or more invalid symbols have been specified.',
141+
301 => 'No date has been specified.',
142+
302 => 'An invalid date has been specified.',
143+
403 => 'No or an invalid amount has been specified.',
144+
501 => 'No or an invalid timeframe has been specified.',
145+
502 => 'No or an invalid "start_date" has been specified.',
146+
503 => 'No or an invalid "end_date" has been specified.',
147+
504 => 'An invalid timeframe has been specified.',
148+
505 => 'The specified timeframe is too long, exceeding 365 days.',
149+
];
150+
151+
return isset($errors[$code]) ? $errors[$code] : '';
152+
}
153+
154+
/**
155+
* {@inheritdoc}
156+
*/
157+
public function getName(): string
158+
{
159+
return 'apilayer_fixer';
160+
}
161+
}

src/Service/Registry.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public static function getServices(): array
5353
'fastforex' => FastForex::class,
5454
'abstract_api' => AbstractApi::class,
5555
'exchangeratehost' => ExchangerateHost::class,
56+
'apilayer_fixer' => ApiLayer\Fixer::class
5657
];
5758
}
5859
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"success": false,
3+
"error": {
4+
"code": 105,
5+
"type": "base_currency_access_restricted"
6+
}
7+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"base": "USD",
3+
"date": "2000-01-03",
4+
"rates": {
5+
"AUD": 1.5209,
6+
"CAD": 1.4447,
7+
"CHF": 1.59,
8+
"CYP": 0.57156,
9+
"CZK": 35.741,
10+
"DKK": 7.374,
11+
"EEK": 15.507,
12+
"GBP": 0.61903,
13+
"HKD": 7.7923,
14+
"HUF": 252.26,
15+
"ISK": 72.379,
16+
"JPY": 101.83,
17+
"KRW": 1129.9,
18+
"LTL": 4.0093,
19+
"LVL": 0.58632,
20+
"MTL": 0.4114,
21+
"NOK": 7.9901,
22+
"NZD": 1.9159,
23+
"PLN": 4.1462,
24+
"ROL": 18110.0,
25+
"SEK": 8.4757,
26+
"SGD": 1.6619,
27+
"SIT": 197.12,
28+
"SKK": 41.94,
29+
"TRL": 541260.0,
30+
"ZAR": 6.146,
31+
"EUR": 0.99108
32+
}
33+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"base": "EUR",
3+
"date": "2016-08-26",
4+
"rates": {
5+
"AUD": 1.4771,
6+
"BGN": 1.9558,
7+
"BRL": 3.6441,
8+
"CAD": 1.4546,
9+
"CHF": 1.0933,
10+
"CNY": 7.5318,
11+
"CZK": 27.024,
12+
"DKK": 7.4462,
13+
"GBP": 0.8545,
14+
"HKD": 8.7555,
15+
"HRK": 7.4893,
16+
"HUF": 308.5,
17+
"IDR": 14906.0,
18+
"ILS": 4.2444,
19+
"INR": 75.647,
20+
"JPY": 113.33,
21+
"KRW": 1258.02,
22+
"MXN": 20.7197,
23+
"MYR": 4.5349,
24+
"NOK": 9.2501,
25+
"NZD": 1.5418,
26+
"PHP": 52.29,
27+
"PLN": 4.3244,
28+
"RON": 4.4578,
29+
"RUB": 73.1392,
30+
"SEK": 9.4903,
31+
"SGD": 1.5269,
32+
"THB": 39.004,
33+
"TRY": 3.316,
34+
"USD": 1.129,
35+
"ZAR": 15.8622
36+
}
37+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Exchanger.
7+
*
8+
* (c) Florian Voutzinos <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Exchanger\Tests\Service\ApiLayer;
15+
16+
use Exchanger\Exception\Exception;
17+
use Exchanger\ExchangeRateQuery;
18+
use Exchanger\HistoricalExchangeRateQuery;
19+
use Exchanger\CurrencyPair;
20+
use Exchanger\Service\ApiLayer\Fixer;
21+
use Exchanger\Tests\Service\ServiceTestCase;
22+
23+
/**
24+
* @author Florian Voutzinos <[email protected]>
25+
*/
26+
class FixerTest extends ServiceTestCase
27+
{
28+
/**
29+
* @test
30+
*/
31+
public function it_supports_all_queries()
32+
{
33+
$service = new Fixer($this->createMock('Http\Client\HttpClient'), null, ['access_key' => 'x']);
34+
$this->assertTrue($service->supportQuery(new ExchangeRateQuery(CurrencyPair::createFromString('USD/EUR'))));
35+
}
36+
37+
/**
38+
* @test
39+
*/
40+
public function it_throws_an_exception_with_error_response()
41+
{
42+
$this->expectException(Exception::class);
43+
$expectedExceptionMessage = 'The current subscription plan does not support this API endpoint.';
44+
$this->expectExceptionMessage($expectedExceptionMessage);
45+
46+
$uri = 'https://api.apilayer.com/fixer/latest?base=USD&apikey=x';
47+
$content = file_get_contents(__DIR__.'/../../../Fixtures/Service/ApiLayer/Fixer/error.json');
48+
49+
$service = new Fixer($this->getHttpAdapterMock($uri, $content), null, ['access_key' => 'x']);
50+
$service->getExchangeRate(new ExchangeRateQuery(CurrencyPair::createFromString('USD/EUR')));
51+
}
52+
53+
/**
54+
* @test
55+
*/
56+
public function it_fetches_a_rate()
57+
{
58+
$pair = CurrencyPair::createFromString('EUR/CHF');
59+
$uri = 'https://api.apilayer.com/fixer/latest?base=EUR&apikey=x';
60+
$content = file_get_contents(__DIR__.'/../../../Fixtures/Service/ApiLayer/Fixer/latest.json');
61+
62+
$service = new Fixer($this->getHttpAdapterMock($uri, $content), null, ['access_key' => 'x']);
63+
$rate = $service->getExchangeRate(new ExchangeRateQuery($pair));
64+
65+
$this->assertEquals(1.0933, $rate->getValue());
66+
$this->assertEquals(new \DateTime('2016-08-26'), $rate->getDate());
67+
$this->assertEquals('apilayer_fixer', $rate->getProviderName());
68+
$this->assertSame($pair, $rate->getCurrencyPair());
69+
}
70+
71+
/**
72+
* @test
73+
*/
74+
public function it_fetches_a_historical_rate()
75+
{
76+
$pair = CurrencyPair::createFromString('EUR/AUD');
77+
$uri = 'https://api.apilayer.com/fixer/2000-01-03?base=EUR&apikey=x';
78+
$content = file_get_contents(__DIR__.'/../../../Fixtures/Service/ApiLayer/Fixer/historical.json');
79+
$date = new \DateTime('2000-01-03');
80+
81+
$service = new Fixer($this->getHttpAdapterMock($uri, $content), null, ['access_key' => 'x']);
82+
$rate = $service->getExchangeRate(new HistoricalExchangeRateQuery($pair, $date));
83+
84+
$this->assertEquals(1.5209, $rate->getValue());
85+
$this->assertEquals($date, $rate->getDate());
86+
$this->assertEquals('apilayer_fixer', $rate->getProviderName());
87+
$this->assertSame($pair, $rate->getCurrencyPair());
88+
}
89+
90+
/**
91+
* @test
92+
*/
93+
public function it_has_a_name()
94+
{
95+
$service = new Fixer($this->createMock('Http\Client\HttpClient'), null, ['access_key' => 'x']);
96+
97+
$this->assertSame('apilayer_fixer', $service->getName());
98+
}
99+
}

0 commit comments

Comments
 (0)