Open Journal Systems  3.3.0
IndirectExchange.php
1 <?php
2 
3 namespace Money\Exchange;
4 
10 use Money\Currency;
13 use Money\Exchange;
14 
20 final class IndirectExchange implements Exchange
21 {
25  private static $calculator;
26 
30  private static $calculators = [
31  BcMathCalculator::class,
32  GmpCalculator::class,
33  PhpCalculator::class,
34  ];
35 
39  private $currencies;
40 
44  private $exchange;
45 
50  public function __construct(Exchange $exchange, Currencies $currencies)
51  {
52  $this->exchange = $exchange;
53  $this->currencies = $currencies;
54  }
55 
59  public static function registerCalculator($calculator)
60  {
61  if (is_a($calculator, Calculator::class, true) === false) {
62  throw new \InvalidArgumentException('Calculator must implement '.Calculator::class);
63  }
64 
65  array_unshift(self::$calculators, $calculator);
66  }
67 
71  public function quote(Currency $baseCurrency, Currency $counterCurrency)
72  {
73  try {
74  return $this->exchange->quote($baseCurrency, $counterCurrency);
75  } catch (UnresolvableCurrencyPairException $exception) {
76  $rate = array_reduce($this->getConversions($baseCurrency, $counterCurrency), function ($carry, CurrencyPair $pair) {
77  return static::getCalculator()->multiply($carry, $pair->getConversionRatio());
78  }, '1.0');
79 
80  return new CurrencyPair($baseCurrency, $counterCurrency, $rate);
81  }
82  }
83 
92  private function getConversions(Currency $baseCurrency, Currency $counterCurrency)
93  {
94  $startNode = $this->initializeNode($baseCurrency);
95  $startNode->discovered = true;
96 
97  $nodes = [$baseCurrency->getCode() => $startNode];
98 
99  $frontier = new \SplQueue();
100  $frontier->enqueue($startNode);
101 
102  while ($frontier->count()) {
104  $currentNode = $frontier->dequeue();
105 
107  $currentCurrency = $currentNode->currency;
108 
109  if ($currentCurrency->equals($counterCurrency)) {
110  return $this->reconstructConversionChain($nodes, $currentNode);
111  }
112 
114  foreach ($this->currencies as $candidateCurrency) {
115  if (!isset($nodes[$candidateCurrency->getCode()])) {
116  $nodes[$candidateCurrency->getCode()] = $this->initializeNode($candidateCurrency);
117  }
118 
120  $node = $nodes[$candidateCurrency->getCode()];
121 
122  if (!$node->discovered) {
123  try {
124  // Check if the candidate is a neighbor. This will throw an exception if it isn't.
125  $this->exchange->quote($currentCurrency, $candidateCurrency);
126 
127  $node->discovered = true;
128  $node->parent = $currentNode;
129 
130  $frontier->enqueue($node);
131  } catch (UnresolvableCurrencyPairException $exception) {
132  // Not a neighbor. Move on.
133  }
134  }
135  }
136  }
137 
138  throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);
139  }
140 
146  private function initializeNode(Currency $currency)
147  {
148  $node = new \stdClass();
149 
150  $node->currency = $currency;
151  $node->discovered = false;
152  $node->parent = null;
153 
154  return $node;
155  }
156 
163  private function reconstructConversionChain(array $currencies, \stdClass $goalNode)
164  {
165  $current = $goalNode;
166  $conversions = [];
167 
168  while ($current->parent) {
169  $previous = $currencies[$current->parent->currency->getCode()];
170  $conversions[] = $this->exchange->quote($previous->currency, $current->currency);
171  $current = $previous;
172  }
173 
174  return array_reverse($conversions);
175  }
176 
180  private function getCalculator()
181  {
182  if (null === self::$calculator) {
183  self::$calculator = self::initializeCalculator();
184  }
185 
186  return self::$calculator;
187  }
188 
194  private static function initializeCalculator()
195  {
196  $calculators = self::$calculators;
197 
198  foreach ($calculators as $calculator) {
200  if ($calculator::supported()) {
201  return new $calculator();
202  }
203  }
204 
205  throw new \RuntimeException('Cannot find calculator for money calculations');
206  }
207 }
Money\Exception\UnresolvableCurrencyPairException
Definition: UnresolvableCurrencyPairException.php:13
Money\Exchange\IndirectExchange
Definition: IndirectExchange.php:20
Money\Calculator\GmpCalculator
Definition: GmpCalculator.php:12
Money\Currencies
Definition: AggregateCurrencies.php:3
Money\Calculator
Definition: BcMathCalculator.php:3
Money\Calculator\BcMathCalculator
Definition: BcMathCalculator.php:12
Money\CurrencyPair
Definition: CurrencyPair.php:12
Money\Exchange
Definition: Exchange.php:12
Money\Currency
Definition: vendor/moneyphp/money/src/Currency.php:14
Money\Currency\getCode
getCode()
Definition: vendor/moneyphp/money/src/Currency.php:47
Money\Exchange\IndirectExchange\quote
quote(Currency $baseCurrency, Currency $counterCurrency)
Definition: IndirectExchange.php:77
Money\CurrencyPair\getConversionRatio
getConversionRatio()
Definition: CurrencyPair.php:114
Money\Exchange\IndirectExchange\__construct
__construct(Exchange $exchange, Currencies $currencies)
Definition: IndirectExchange.php:56
Money\Calculator\PhpCalculator
Definition: PhpCalculator.php:12
Money\Exchange\IndirectExchange\registerCalculator
static registerCalculator($calculator)
Definition: IndirectExchange.php:65
Money\Currencies
Definition: Currencies.php:12
Money\Exchange
Definition: ExchangerExchange.php:3
Currency
Basic class describing a currency.
Definition: Currency.inc.php:24
Money\Exception\UnresolvableCurrencyPairException\createFromCurrencies
static createFromCurrencies(Currency $baseCurrency, Currency $counterCurrency)
Definition: UnresolvableCurrencyPairException.php:23