moneyphp / money

PHP implementation of Fowler's Money pattern.
http://moneyphp.org
MIT License
4.62k stars 440 forks source link

Add underlying provider name to the currency pair #710

Closed morloderex closed 2 years ago

morloderex commented 2 years ago

Hey MoneyPHP team.

I am doing some currency conversations using the swap implementation as the baseline, which internally uses the exchange framework behind the scenes using its chain functionality.

I have been asked to save the conversion rate and the provider that was used to fetch that conversion rate somewhere. Which brings up a question that i am trying to solve "How can i get the provider that was used to fetch the conversion rate?"

I believe i have managed to solve that issue for the different implementations that we have for our exchange functionality by also allowing the "CurrencyPair" to hold that information for us.

I hope this will make it in so i can get the context i need.

If changing method signatures is considered a breaking change i would be open for changing that around.

frederikbosch commented 2 years ago

You should solve this on your own end. Create a new (value) object and your own exchange.


<?php
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;

use function assert;
use function is_numeric;
use function sprintf;

final class ProvidedCurrencyPair 
{
    private CurrencyPair $pair;
    private string providerName;

    public function __construct(CurrencyPair $pair, string $providerName) 
    {
        $this->pair = $pair;
        $this->providerName = $providerName;
    }

    public function getPair(): CurrencyPair
    {
         return $this->pair;
    }

    public function getProviderName(): string
    {
         return $this->providerName;
    }
}

final class MyExchange implements Exchange 
{
    private ExchangeRateProvider $exchanger;

    public function __construct(ExchangeRateProvider $exchanger)
    {
        $this->exchanger = $exchanger;
    }

    public function quoteWithProviderName(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair
    {
        try {
            $query = new ExchangeRateQuery(
                new ExchangerCurrencyPair($baseCurrency->getCode(), $counterCurrency->getCode())
            );
            $rate  = $this->exchanger->getExchangeRate($query);
        } catch (ExchangerException) {
            throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);
        }

        $rateValue = sprintf('%.14F', $rate->getValue());

        assert(is_numeric($rateValue));

        return new ProvidedCurrencyPair(
            new CurrencyPair($baseCurrency, $counterCurrency, $rateValue), 
            $rate->getProviderName()
        );
    }

    public function quote(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair 
    {
        return $this->decoratedExchange->quoteWithProvider($baseCurrency, $counterCurrency)->getPair();
    }
}
morloderex commented 2 years ago

Hey @frederikbosch Sorry for the late reply.

But would you maybe consider dropping final keyword on the \Money\CurrencyPair so I can easier implement this logic, because with the class locked for inheritance i cannot simply use the build in \Money\Converter class to call convertAndReturnWithCurrencyPair sense what i want to do using the idea from your example above is to extend the CurrencyPair and grab the providerName up the chain along the with the converted moneyphp/money instance.

frederikbosch commented 2 years ago

Not dropping the final keyword, anywhere. You don't need to extend, you need a new class, e.g. ProvidedCurrencyPair as I called in the example.

morloderex commented 2 years ago

@frederikbosch Okay can you give me an example where i can get the providerName from calling.

[$converted, $pair] = (new \Money\Converter())->convertAndReturnWithCurrencyPair(new \Money\Money(1000, new \Money\Currency('EUR')), new \Money\Currency('DKK'))

Without having to reinvent the \Money\Converter class also sense i cannot see how i can just decorate the \Money\CurrencyPair class, as i need it further up the stack chain?