Skip to content

Commit

Permalink
Add exchange for retrying with reversed currencies
Browse files Browse the repository at this point in the history
  • Loading branch information
sagikazarmark committed Nov 7, 2016
1 parent 4e09a82 commit 2df935b
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 2 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Change Log


## UNRELEASED

### Added

- Reversed Currencies Exchange to try resolving reverse of a currency pair


## 3.0.0 - 2016-10-26

### Added
Expand Down
37 changes: 35 additions & 2 deletions doc/features/currency-conversion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ To convert a Money instance from one Currency to another, you need the Converter
Currencies and Exchange. Exchange returns a `CurrencyPair`, which is the combination of the base
currency, counter currency and the conversion ratio.


Fixed Exchange
--------------

Expand All @@ -27,6 +28,38 @@ You can use a fixed exchange to convert `Money` into another Currency.
$eur100 = Money::EUR(100);
$usd125 = $converter->convert($eur100, new Currency('USD'));
Reversed Currencies Exchange
----------------------------

In some cases you might want the Exchange to resolve the reverse of the Currency Pair
as well if the original cannot be found. To add this behaviour to any Exchange
you need to wrap it in in a `ReversedCurrenciesExchange`. If a reverse Currency Pair
can be found, it's simply used as a divisor of 1 to calculate the reverse
conversion ratio.

For example this can be useful if you use a `FixedExchange` and you don't want to
define the currency pairs in both directions.

.. code:: php
use Money\Converter;
use Money\Currency;
use Money\Exchange\FixedExchange;
use Money\Exchange\ReversedCurrenciesExchange;
$exchange = new ReversedCurrenciesExchange(new FixedExchange([
'EUR' => [
'USD' => 1.25
]
]));
$converter = new Converter(new ISOCurrencies(), $exchange);
$usd125 = Money::USD(125);
$eur100 = $converter->convert($usd125, new Currency('EUR'));
Third Party Exchange
--------------------

Expand All @@ -51,7 +84,7 @@ Then conversion is quite simple:
$converter = new Converter(new ISOCurrencies(), $exchange);
$eur100 = Money::EUR(100);
$usd125 = $converter->convert($eur100, $pair);
$usd125 = $converter->convert($eur100, new Currency('USD'));
.. _Swap: https://github.com/florianv/swap
Expand Down Expand Up @@ -95,4 +128,4 @@ which you can install via Composer_.
// $swap = Implementation of \Swap\SwapInterface
$exchange = new SwapExchange($swap);
$pair = $exchange->quote($eur, $usd);
$pair = $exchange->quote($eur, $usd);
2 changes: 2 additions & 0 deletions doc/spelling_word_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ Hieatt
Mee
USD
greaterThanOrEqual

behaviour
69 changes: 69 additions & 0 deletions spec/Exchange/ReversedCurrenciesExchangeSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace spec\Money\Exchange;

use Money\Currency;
use Money\CurrencyPair;
use Money\Exception\UnresolvableCurrencyPairException;
use Money\Exchange;
use Money\Exchange\ReversedCurrenciesExchange;
use PhpSpec\ObjectBehavior;

final class ReversedCurrenciesExchangeSpec extends ObjectBehavior
{
function let(Exchange $exchange)
{
$this->beConstructedWith($exchange);
}

function it_is_initializable()
{
$this->shouldHaveType(ReversedCurrenciesExchange::class);
}

function it_is_an_exchange()
{
$this->shouldImplement(Exchange::class);
}

function it_exchanges_currencies(Exchange $exchange)
{
$baseCurrency = new Currency('EUR');
$counterCurrency = new Currency('USD');
$currencyPair = new CurrencyPair($baseCurrency, $counterCurrency, 1.25);

$exchange->quote($baseCurrency, $counterCurrency)->willReturn($currencyPair);

$this->quote($baseCurrency, $counterCurrency)->shouldreturn($currencyPair);
}

function it_exchanges_reversed_currencies_when_the_original_pair_is_not_found(Exchange $exchange)
{
$baseCurrency = new Currency('USD');
$counterCurrency = new Currency('EUR');
$conversionRatio = 1.25;
$currencyPair = new CurrencyPair($counterCurrency, $baseCurrency, $conversionRatio);

$exchange->quote($baseCurrency, $counterCurrency)->willThrow(UnresolvableCurrencyPairException::class);
$exchange->quote($counterCurrency, $baseCurrency)->willReturn($currencyPair);

$currencyPair = $this->quote($baseCurrency, $counterCurrency);

$currencyPair->shouldHaveType(CurrencyPair::class);
$currencyPair->getBaseCurrency()->shouldReturn($baseCurrency);
$currencyPair->getCounterCurrency()->shouldReturn($counterCurrency);
$currencyPair->getConversionRatio()->shouldReturn(1 / $conversionRatio);
}

function it_throws_an_exception_when_neither_the_original_nor_the_reversed_currency_pair_can_be_resolved(Exchange $exchange)
{
$baseCurrency = new Currency('USD');
$counterCurrency = new Currency('EUR');

// Exceptions are not matched based on identity, but instance and properties
$exchange->quote($baseCurrency, $counterCurrency)->willThrow(UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency));
$exchange->quote($counterCurrency, $baseCurrency)->willThrow(UnresolvableCurrencyPairException::createFromCurrencies($counterCurrency, $baseCurrency));

$this->shouldThrow(UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency))->duringQuote($baseCurrency, $counterCurrency);
}
}
49 changes: 49 additions & 0 deletions src/Exchange/ReversedCurrenciesExchange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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 <[email protected]>
*/
final class ReversedCurrenciesExchange implements Exchange
{
/**
* @var Exchange
*/
private $exchange;

/**
* @param Exchange $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;
}
}
}
}

0 comments on commit 2df935b

Please sign in to comment.