PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / moneyphp / money / src / Exchange / IndirectExchange.php
ameliabooking / vendor / moneyphp / money / src / Exchange Last commit date
ExchangerExchange.php 2 years ago FixedExchange.php 2 years ago IndirectExchange.php 2 years ago ReversedCurrenciesExchange.php 2 years ago SwapExchange.php 2 years ago
IndirectExchange.php
196 lines
1 <?php
2
3 namespace Money\Exchange;
4
5 use Money\Calculator;
6 use Money\Calculator\BcMathCalculator;
7 use Money\Calculator\GmpCalculator;
8 use Money\Calculator\PhpCalculator;
9 use Money\Currencies;
10 use Money\Currency;
11 use Money\CurrencyPair;
12 use Money\Exception\UnresolvableCurrencyPairException;
13 use Money\Exchange;
14
15 /**
16 * Provides a way to get an exchange rate through a minimal set of intermediate conversions.
17 *
18 * @author Michael Cordingley <Michael.Cordingley@gmail.com>
19 */
20 final class IndirectExchange implements Exchange
21 {
22 /**
23 * @var Calculator
24 */
25 private static $calculator;
26
27 /**
28 * @var array
29 */
30 private static $calculators = [
31 BcMathCalculator::class,
32 GmpCalculator::class,
33 PhpCalculator::class,
34 ];
35
36 /**
37 * @var Currencies
38 */
39 private $currencies;
40
41 /**
42 * @var Exchange
43 */
44 private $exchange;
45
46 public function __construct(Exchange $exchange, Currencies $currencies)
47 {
48 $this->exchange = $exchange;
49 $this->currencies = $currencies;
50 }
51
52 /**
53 * @param string $calculator
54 */
55 public static function registerCalculator($calculator)
56 {
57 if (is_a($calculator, Calculator::class, true) === false) {
58 throw new \InvalidArgumentException('Calculator must implement '.Calculator::class);
59 }
60
61 array_unshift(self::$calculators, $calculator);
62 }
63
64 /**
65 * {@inheritdoc}
66 */
67 public function quote(Currency $baseCurrency, Currency $counterCurrency)
68 {
69 try {
70 return $this->exchange->quote($baseCurrency, $counterCurrency);
71 } catch (UnresolvableCurrencyPairException $exception) {
72 $rate = array_reduce($this->getConversions($baseCurrency, $counterCurrency), function ($carry, CurrencyPair $pair) {
73 return static::getCalculator()->multiply($carry, $pair->getConversionRatio());
74 }, '1.0');
75
76 return new CurrencyPair($baseCurrency, $counterCurrency, $rate);
77 }
78 }
79
80 /**
81 * @return CurrencyPair[]
82 *
83 * @throws UnresolvableCurrencyPairException
84 */
85 private function getConversions(Currency $baseCurrency, Currency $counterCurrency)
86 {
87 $startNode = $this->initializeNode($baseCurrency);
88 $startNode->discovered = true;
89
90 $nodes = [$baseCurrency->getCode() => $startNode];
91
92 $frontier = new \SplQueue();
93 $frontier->enqueue($startNode);
94
95 while ($frontier->count()) {
96 /** @var \stdClass $currentNode */
97 $currentNode = $frontier->dequeue();
98
99 /** @var Currency $currentCurrency */
100 $currentCurrency = $currentNode->currency;
101
102 if ($currentCurrency->equals($counterCurrency)) {
103 return $this->reconstructConversionChain($nodes, $currentNode);
104 }
105
106 /** @var Currency $candidateCurrency */
107 foreach ($this->currencies as $candidateCurrency) {
108 if (!isset($nodes[$candidateCurrency->getCode()])) {
109 $nodes[$candidateCurrency->getCode()] = $this->initializeNode($candidateCurrency);
110 }
111
112 /** @var \stdClass $node */
113 $node = $nodes[$candidateCurrency->getCode()];
114
115 if (!$node->discovered) {
116 try {
117 // Check if the candidate is a neighbor. This will throw an exception if it isn't.
118 $this->exchange->quote($currentCurrency, $candidateCurrency);
119
120 $node->discovered = true;
121 $node->parent = $currentNode;
122
123 $frontier->enqueue($node);
124 } catch (UnresolvableCurrencyPairException $exception) {
125 // Not a neighbor. Move on.
126 }
127 }
128 }
129 }
130
131 throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);
132 }
133
134 /**
135 * @return \stdClass
136 */
137 private function initializeNode(Currency $currency)
138 {
139 $node = new \stdClass();
140
141 $node->currency = $currency;
142 $node->discovered = false;
143 $node->parent = null;
144
145 return $node;
146 }
147
148 /**
149 * @return CurrencyPair[]
150 */
151 private function reconstructConversionChain(array $currencies, \stdClass $goalNode)
152 {
153 $current = $goalNode;
154 $conversions = [];
155
156 while ($current->parent) {
157 $previous = $currencies[$current->parent->currency->getCode()];
158 $conversions[] = $this->exchange->quote($previous->currency, $current->currency);
159 $current = $previous;
160 }
161
162 return array_reverse($conversions);
163 }
164
165 /**
166 * @return Calculator
167 */
168 private function getCalculator()
169 {
170 if (null === self::$calculator) {
171 self::$calculator = self::initializeCalculator();
172 }
173
174 return self::$calculator;
175 }
176
177 /**
178 * @return Calculator
179 *
180 * @throws \RuntimeException If cannot find calculator for money calculations
181 */
182 private static function initializeCalculator()
183 {
184 $calculators = self::$calculators;
185
186 foreach ($calculators as $calculator) {
187 /** @var Calculator $calculator */
188 if ($calculator::supported()) {
189 return new $calculator();
190 }
191 }
192
193 throw new \RuntimeException('Cannot find calculator for money calculations');
194 }
195 }
196