new appraoch

This commit is contained in:
Tim Bendt
2025-11-26 13:22:58 -05:00
parent de3d100844
commit c520b7df89
6760 changed files with 1009780 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
<?php
namespace Money\Exchange;
use Exchanger\Contract\ExchangeRateProvider;
use Exchanger\CurrencyPair as ExchangerCurrencyPair;
use Exchanger\Exception\Exception as ExchangerException;
use Exchanger\ExchangeRateQuery;
use Money\Currency;
use Money\CurrencyPair;
use Money\Exception\UnresolvableCurrencyPairException;
use Money\Exchange;
/**
* Provides a way to get exchange rate from a third-party source and return a currency pair.
*
* @author Maksim (Ellrion) Platonov <ellrion11@gmail.com>
*/
final class ExchangerExchange implements Exchange
{
/**
* @var ExchangeRateProvider
*/
private $exchanger;
public function __construct(ExchangeRateProvider $exchanger)
{
$this->exchanger = $exchanger;
}
/**
* {@inheritdoc}
*/
public function quote(Currency $baseCurrency, Currency $counterCurrency)
{
try {
$query = new ExchangeRateQuery(
new ExchangerCurrencyPair($baseCurrency->getCode(), $counterCurrency->getCode())
);
$rate = $this->exchanger->getExchangeRate($query);
} catch (ExchangerException $e) {
throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);
}
return new CurrencyPair($baseCurrency, $counterCurrency, $rate->getValue());
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Money\Exchange;
use Money\Currency;
use Money\CurrencyPair;
use Money\Exception\UnresolvableCurrencyPairException;
use Money\Exchange;
/**
* Provides a way to get exchange rate from a static list (array).
*
* @author Frederik Bosch <f.bosch@genkgo.nl>
*/
final class FixedExchange implements Exchange
{
/**
* @var array
*/
private $list;
public function __construct(array $list)
{
$this->list = $list;
}
/**
* {@inheritdoc}
*/
public function quote(Currency $baseCurrency, Currency $counterCurrency)
{
if (isset($this->list[$baseCurrency->getCode()][$counterCurrency->getCode()])) {
return new CurrencyPair(
$baseCurrency,
$counterCurrency,
$this->list[$baseCurrency->getCode()][$counterCurrency->getCode()]
);
}
throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);
}
}

View File

@@ -0,0 +1,195 @@
<?php
namespace Money\Exchange;
use Money\Calculator;
use Money\Calculator\BcMathCalculator;
use Money\Calculator\GmpCalculator;
use Money\Calculator\PhpCalculator;
use Money\Currencies;
use Money\Currency;
use Money\CurrencyPair;
use Money\Exception\UnresolvableCurrencyPairException;
use Money\Exchange;
/**
* Provides a way to get an exchange rate through a minimal set of intermediate conversions.
*
* @author Michael Cordingley <Michael.Cordingley@gmail.com>
*/
final class IndirectExchange implements Exchange
{
/**
* @var Calculator
*/
private static $calculator;
/**
* @var array
*/
private static $calculators = [
BcMathCalculator::class,
GmpCalculator::class,
PhpCalculator::class,
];
/**
* @var Currencies
*/
private $currencies;
/**
* @var Exchange
*/
private $exchange;
public function __construct(Exchange $exchange, Currencies $currencies)
{
$this->exchange = $exchange;
$this->currencies = $currencies;
}
/**
* @param string $calculator
*/
public static function registerCalculator($calculator)
{
if (is_a($calculator, Calculator::class, true) === false) {
throw new \InvalidArgumentException('Calculator must implement '.Calculator::class);
}
array_unshift(self::$calculators, $calculator);
}
/**
* {@inheritdoc}
*/
public function quote(Currency $baseCurrency, Currency $counterCurrency)
{
try {
return $this->exchange->quote($baseCurrency, $counterCurrency);
} catch (UnresolvableCurrencyPairException $exception) {
$rate = array_reduce($this->getConversions($baseCurrency, $counterCurrency), function ($carry, CurrencyPair $pair) {
return static::getCalculator()->multiply($carry, $pair->getConversionRatio());
}, '1.0');
return new CurrencyPair($baseCurrency, $counterCurrency, $rate);
}
}
/**
* @return CurrencyPair[]
*
* @throws UnresolvableCurrencyPairException
*/
private function getConversions(Currency $baseCurrency, Currency $counterCurrency)
{
$startNode = $this->initializeNode($baseCurrency);
$startNode->discovered = true;
$nodes = [$baseCurrency->getCode() => $startNode];
$frontier = new \SplQueue();
$frontier->enqueue($startNode);
while ($frontier->count()) {
/** @var \stdClass $currentNode */
$currentNode = $frontier->dequeue();
/** @var Currency $currentCurrency */
$currentCurrency = $currentNode->currency;
if ($currentCurrency->equals($counterCurrency)) {
return $this->reconstructConversionChain($nodes, $currentNode);
}
/** @var Currency $candidateCurrency */
foreach ($this->currencies as $candidateCurrency) {
if (!isset($nodes[$candidateCurrency->getCode()])) {
$nodes[$candidateCurrency->getCode()] = $this->initializeNode($candidateCurrency);
}
/** @var \stdClass $node */
$node = $nodes[$candidateCurrency->getCode()];
if (!$node->discovered) {
try {
// Check if the candidate is a neighbor. This will throw an exception if it isn't.
$this->exchange->quote($currentCurrency, $candidateCurrency);
$node->discovered = true;
$node->parent = $currentNode;
$frontier->enqueue($node);
} catch (UnresolvableCurrencyPairException $exception) {
// Not a neighbor. Move on.
}
}
}
}
throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);
}
/**
* @return \stdClass
*/
private function initializeNode(Currency $currency)
{
$node = new \stdClass();
$node->currency = $currency;
$node->discovered = false;
$node->parent = null;
return $node;
}
/**
* @return CurrencyPair[]
*/
private function reconstructConversionChain(array $currencies, \stdClass $goalNode)
{
$current = $goalNode;
$conversions = [];
while ($current->parent) {
$previous = $currencies[$current->parent->currency->getCode()];
$conversions[] = $this->exchange->quote($previous->currency, $current->currency);
$current = $previous;
}
return array_reverse($conversions);
}
/**
* @return Calculator
*/
private function getCalculator()
{
if (null === self::$calculator) {
self::$calculator = self::initializeCalculator();
}
return self::$calculator;
}
/**
* @return Calculator
*
* @throws \RuntimeException If cannot find calculator for money calculations
*/
private static function initializeCalculator()
{
$calculators = self::$calculators;
foreach ($calculators as $calculator) {
/** @var Calculator $calculator */
if ($calculator::supported()) {
return new $calculator();
}
}
throw new \RuntimeException('Cannot find calculator for money calculations');
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Money\Exchange;
use Money\Currency;
use Money\CurrencyPair;
use Money\Exception\UnresolvableCurrencyPairException;
use Money\Exchange;
/**
* Tries the reverse of the currency pair if one is not available.
*
* Note: adding nested ReversedCurrenciesExchange could cause a huge performance hit.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class ReversedCurrenciesExchange implements Exchange
{
/**
* @var Exchange
*/
private $exchange;
public function __construct(Exchange $exchange)
{
$this->exchange = $exchange;
}
/**
* {@inheritdoc}
*/
public function quote(Currency $baseCurrency, Currency $counterCurrency)
{
try {
return $this->exchange->quote($baseCurrency, $counterCurrency);
} catch (UnresolvableCurrencyPairException $exception) {
try {
$currencyPair = $this->exchange->quote($counterCurrency, $baseCurrency);
return new CurrencyPair($baseCurrency, $counterCurrency, 1 / $currencyPair->getConversionRatio());
} catch (UnresolvableCurrencyPairException $inversedException) {
throw $exception;
}
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Money\Exchange;
use Exchanger\Exception\Exception as ExchangerException;
use Money\Currency;
use Money\CurrencyPair;
use Money\Exception\UnresolvableCurrencyPairException;
use Money\Exchange;
use Swap\Swap;
/**
* Provides a way to get exchange rate from a third-party source and return a currency pair.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class SwapExchange implements Exchange
{
/**
* @var Swap
*/
private $swap;
public function __construct(Swap $swap)
{
$this->swap = $swap;
}
/**
* {@inheritdoc}
*/
public function quote(Currency $baseCurrency, Currency $counterCurrency)
{
try {
$rate = $this->swap->latest($baseCurrency->getCode().'/'.$counterCurrency->getCode());
} catch (ExchangerException $e) {
throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);
}
return new CurrencyPair($baseCurrency, $counterCurrency, $rate->getValue());
}
}