Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.12% covered (success)
94.12%
32 / 34
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Currency
94.12% covered (success)
94.12%
32 / 34
80.00% covered (warning)
80.00%
4 / 5
20.08
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 resetCurrencies
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromEurTo
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getEcbEuroRates
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
7.14
 fromToEur
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 convertCurrency
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Utils\Converter
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Utils\Converter;
16
17use phpOMS\Localization\ISO4217CharEnum;
18use phpOMS\Message\Http\HttpRequest;
19use phpOMS\Message\Http\RequestMethod;
20use phpOMS\Message\Http\Rest;
21use phpOMS\Uri\HttpUri;
22
23/**
24 * Currency converter.
25 *
26 * @package phpOMS\Utils\Converter
27 * @license OMS License 2.0
28 * @link    https://jingga.app
29 * @since   1.0.0
30 */
31final class Currency
32{
33    /**
34     * ECB currency rates.
35     *
36     * @var array<string, float>
37     * @since 1.0.0
38     */
39    private static array $ecbCurrencies = [];
40
41    /**
42     * Constructor.
43     *
44     * @since 1.0.0
45     * @codeCoverageIgnore
46     */
47    private function __construct()
48    {
49    }
50
51    /**
52     * Reset currency rates.
53     *
54     * Can be used in order to refresh them. Be careful currency rates only get updated once a day from the ECB website.
55     *
56     * @return void
57     *
58     * @since 1.0.0
59     */
60    public static function resetCurrencies() : void
61    {
62        self::$ecbCurrencies = [];
63    }
64
65    /**
66     * Convert from EUR
67     *
68     * @param float  $value Value to convert
69     * @param string $to    Output currency
70     *
71     * @return float
72     *
73     * @since 1.0.0
74     */
75    public static function fromEurTo(float $value, string $to) : float
76    {
77        $currencies = self::getEcbEuroRates();
78        $to         = \strtoupper($to);
79
80        if (!isset($currencies[$to])) {
81            return -1.0;
82        }
83
84        return $value * $currencies[$to];
85    }
86
87    /**
88     * Get ECB currency rates.
89     *
90     * @return array<string, float>
91     *
92     * @throws \Exception This exception is thrown if the XML is malformed
93     *
94     * @since 1.0.0
95     */
96    public static function getEcbEuroRates() : array
97    {
98        if (!empty(self::$ecbCurrencies)) {
99            return self::$ecbCurrencies;
100        }
101
102        $request = new HttpRequest(new HttpUri('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'));
103        $request->setMethod(RequestMethod::GET);
104
105        try {
106            $xml = new \SimpleXMLElement(Rest::request($request)->getBody());
107
108            if (!(\property_exists($xml, 'Cube') && $xml->Cube !== null)) {
109                throw new \Exception('Invalid xml path'); // @codeCoverageIgnore
110            }
111
112            $node                = $xml->Cube->Cube->Cube;
113            self::$ecbCurrencies = [];
114
115            foreach ($node as $value) {
116                /** @var null|array<string, string|int|float> $attributes */
117                if (($attributes = $value->attributes()) === null) {
118                    continue;
119                }
120
121                self::$ecbCurrencies[\strtoupper((string) ($attributes['currency']))] = (float) ($attributes['rate']);
122            }
123        } catch (\Throwable $_) {
124            self::$ecbCurrencies = []; // @codeCoverageIgnore
125        }
126
127        return self::$ecbCurrencies;
128    }
129
130    /**
131     * Convert to EUR
132     *
133     * @param float  $value Value to convert
134     * @param string $from  Input currency
135     *
136     * @return float
137     *
138     * @since 1.0.0
139     */
140    public static function fromToEur(float $value, string $from) : float
141    {
142        $currencies = self::getEcbEuroRates();
143        $from       = \strtoupper($from);
144
145        if (!isset($currencies[$from])) {
146            return -1.0;
147        }
148
149        return $value / $currencies[$from];
150    }
151
152    /**
153     * Convert currency based on ECB reates
154     *
155     * @param float  $value Value to convert
156     * @param string $from  Input currency
157     * @param string $to    Output currency
158     *
159     * @return float
160     *
161     * @since 1.0.0
162     */
163    public static function convertCurrency(float $value, string $from, string $to) : float
164    {
165        $currencies = self::getEcbEuroRates();
166        $from       = \strtoupper($from);
167        $to         = \strtoupper($to);
168
169        if ((!isset($currencies[$from]) && $from !== ISO4217CharEnum::_EUR)
170            || (!isset($currencies[$to]) && $to !== ISO4217CharEnum::_EUR)
171        ) {
172            return -1.0;
173        }
174
175        if ($from !== ISO4217CharEnum::_EUR) {
176            $value /= $currencies[$from];
177        }
178
179        return $to === ISO4217CharEnum::_EUR ? $value : $value * $currencies[$to];
180    }
181}