Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
70.83% covered (warning)
70.83%
17 / 24
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExchangeRateService
70.83% covered (warning)
70.83%
17 / 24
33.33% covered (danger)
33.33%
1 / 3
8.22
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 calculateConversionRate
68.42% covered (warning)
68.42%
13 / 19
0.00% covered (danger)
0.00%
0 / 1
4.50
 normalizeCurrency
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2namespace Apie\ExchangeRate;
3
4use Symfony\Component\HttpClient\HttpClient;
5use Symfony\Contracts\HttpClient\HttpClientInterface;
6
7class ExchangeRateService
8{
9    private const API_URL = 'https://api.frankfurter.app/latest';
10
11    public function __construct(private ?HttpClientInterface $client = null)
12    {
13        $this->client ??= HttpClient::create();
14    }
15
16    public function calculateConversionRate(string $fromCurrency, string $toCurrency): float
17    {
18        $fromCurrency = $this->normalizeCurrency($fromCurrency);
19        $toCurrency = $this->normalizeCurrency($toCurrency);
20
21        if ($fromCurrency === $toCurrency) {
22            return 1.0;
23        }
24
25        $response = $this->client->request('GET', self::API_URL, [
26            'query' => [
27                'from' => $fromCurrency,
28                'to' => $toCurrency,
29            ],
30        ]);
31        $data = $response->toArray();
32        $rate = $data['rates'][$toCurrency] ?? null;
33
34        if (!is_int($rate) && !is_float($rate)) {
35            throw new \RuntimeException(sprintf(
36                'The exchange rate API did not return a rate for %s to %s.',
37                $fromCurrency,
38                $toCurrency
39            ));
40        }
41
42        return (float) $rate;
43    }
44
45    private function normalizeCurrency(string $currency): string
46    {
47        $currency = strtoupper(trim($currency));
48
49        if (!preg_match('/^[A-Z]{3}$/', $currency)) {
50            throw new \InvalidArgumentException('Currency codes must contain exactly three letters.');
51        }
52
53        return $currency;
54    }
55}