Giter VIP home page Giter VIP logo

Comments (20)

BenMorel avatar BenMorel commented on July 22, 2024 2

@Big-Shark There is no way around the fact that you cannot have Money::of(1, 'BTC'); in your code without relying on a global context. A global currency provider is bad, as Money may be used in your app, but also in libraries etc., and conflicts may occur.

The only possible solution is a factory and DI. And if you need to instantiate a custom currency from within a value object, you need to refactor it so that the VO method accepts a Currency instance instead of a currency code, or takes the factory as a parameter.

from money.

miroslav-chandler avatar miroslav-chandler commented on July 22, 2024 1

I faced the same issue with crypto currency.

How about making CurrencyInterface for Currency class and remove the final?

In that case, anybody will have the opportunity to extend Currency class and make their own providers and redeclare the __construct to avoid redundant Currency declaration.

from money.

BenMorel avatar BenMorel commented on July 22, 2024

Hi, this has been discussed to some extent in #5.

I know this is a common requirement, however as you guessed, I’m against any type of global state... Maybe your best bet is to create a MoneyFactory that would replicate the of() and ofMinor() factory methods, while transparently accepting custom currency codes?

I'm actually thinking that this MoneyFactory could be provided by the library; you’d only have to implement a CurrencyProvider that takes a currency code and returns a Currency instance. Here is how it would look like:

class MyCurrencyProvider implements CurrencyProvider
{
    public function getCurrency($currencyCode): Currency
    {
        switch ($currencyCode) {
            case 'BTC':
                return new Currency(...);

            // ... other currencies as required
        }

        // handle ISO currencies as normal

        return ISOCurrencyProvider::getInstance()->getCurrency($currencyCode);
    }
}

$factory = new MoneyFactory(new MyCurrencyProvider);

$money = $factory->of('0.0123', 'BTC'); // use this instead of Money::of()

Would this work for you?

from money.

francislavoie avatar francislavoie commented on July 22, 2024

Yeah! That's pretty good. MoneyFactory could easily be dependency injected etc. I like it.

from money.

BenMorel avatar BenMorel commented on July 22, 2024

I actually gave it a shot, but I feel like this MoneyFactory does not bring much value:

final class MoneyFactory
{
    /**
     * @var CurrencyProvider
     */
    private $currencyProvider;

    /**
     * @param CurrencyProvider $currencyProvider
     */
    public function __construct(CurrencyProvider $currencyProvider)
    {
        $this->currencyProvider = $currencyProvider;
    }

    public function of($amount, $currency, ?Context $context = null, int $roundingMode = RoundingMode::UNNECESSARY) : Money
    {
        if (! $currency instanceof Currency) {
            $currency = $this->currencyProvider->getCurrency($currency);
        }

        return Money::of($amount, $currency, $context, $roundingMode);
    }

    public function ofMinor($minorAmount, $currency, ?Context $context = null, int $roundingMode = RoundingMode::UNNECESSARY) : Money
    {
        if (! $currency instanceof Currency) {
            $currency = $this->currencyProvider->getCurrency($currency);
        }

        return Money::ofMinor($minorAmount, $currency, $context, $roundingMode);
    }
}

Sure, it allows you to do:

$money = $factory->of('0.0123', 'BTC');

But is it much better than this?

$money = Money::of('0.0123', $currencyProvider->getCurrency('BTC'));

from money.

BenMorel avatar BenMorel commented on July 22, 2024

Another option could be to pass the CurrencyProvider to of(), but the method is already bloated:

$money = Money::of('0.0123', 'BTC', null, RoundingMode::UNNECESSARY, $currencyProvider);

from money.

francislavoie avatar francislavoie commented on July 22, 2024

Another advantage to the factory is that you could specify a different default context or rounding mode for all monies being created - for example I think I'd want to always use HALF_UP rounding for all the monies in a particular part of my app.

$factory = (new MoneyFactory)
	->withCurrencyProvider($provider)
	->withRoundingMode(RoundingMode::HALF_UP);
$money = $factory->of('0.0123', 'BTC');

from money.

BenMorel avatar BenMorel commented on July 22, 2024

Interesting idea, though using a default rounding mode potentially means that you change the MoneyFactory::of() signature to not include a RoundingMode? Or make it nullable, so that it defaults to the one configured in the factory.

This may work, but I'm not sure if I see a value in using a default rounding mode!

from money.

francislavoie avatar francislavoie commented on July 22, 2024

Fair enough - I can always just set up the factory idea in my own project if it's not shipped with the library 🙂

from money.

BenMorel avatar BenMorel commented on July 22, 2024

I pushed a proof of concept here: 62f7eea

Comments welcome.

from money.

francislavoie avatar francislavoie commented on July 22, 2024

Nice. I like it.

Did you consider the withCurrencyProvider approach instead of making the constructor params nullable? I could imagine someone would want to override the context but not the currencies or rounding, etc. With "withers", you could just set up any combination of those 3 things without say, needing to pass null for one or more of the params.

from money.

BenMorel avatar BenMorel commented on July 22, 2024

I did, actually. Would you expect this to return a new instance?

from money.

francislavoie avatar francislavoie commented on July 22, 2024

🤔 I don't think it would need to, as a factory. I think return $this; fluent interface is appropriate for this. I don't think the withers for this sort of factory needs to be immutable.

from money.

Big-Shark avatar Big-Shark commented on July 22, 2024

@BenMorel @francislavoie What do you think about this api

ISOCurrencyProvider::getInstance()->addCurrency(new Currency(
    'XBT',     // currency code
    0,         // numeric currency code, useful when storing monies in a database; set to 0 if unused
    'Bitcoin', // currency name
    8          // default scale
));

// And 
Money::of('0.0123', 'BTC);

or we can create AggregateCurrencyProvider and use something like that

AggregateCurrencyProvider::getInstance()->addCurrencyProvider(new CryptocurrencyProvider());

and use AggregateCurrencyProvider in currency class, but we will need ISOCurrencyProvider in constructorby default

from money.

BenMorel avatar BenMorel commented on July 22, 2024

@Big-Shark That's creating a global context, which is usually bad (ISOCurrencyProvider is fine, as it's immutable).

You'll want to pass your CurrencyProvider around instead, probably using your DI container.

from money.

Big-Shark avatar Big-Shark commented on July 22, 2024

@BenMorel yes, but when we will use MoneyFactory we need to use DI for creating VO, and we can not use this in entity.
We need thinking, how we can use same api for iso currency, and cryptocurrency in one project.

from money.

Big-Shark avatar Big-Shark commented on July 22, 2024

@BenMorel Maybe we can add CryptoCurrencyProvider in this repository too? I think we can add maybe one hundred most popular cryptocurrencies.

from money.

BenMorel avatar BenMorel commented on July 22, 2024

@Big-Shark The problem with crypto-currencies is that they may come and go, there is no stable, official list of currencies like the one provided with the ISO organization!

from money.

Big-Shark avatar Big-Shark commented on July 22, 2024

@BenMorel Yeah, I understand this, but usually, people use 10 or maybe 20 crypto currencies, but we can add 100 most popular cryptocurrencies, and I think this will be enaughf for the first time, but if some bode need more, he can create PR.

from money.

BrianHenryIE avatar BrianHenryIE commented on July 22, 2024

I added a Composer script to edit the iso-currencies.php file:

{
  "scripts": {
    "post-install-cmd": [
      "@add-btc-to-brick-money"
    ],
    "post-update-cmd": [
      "@add-btc-to-brick-money"
    ],
    "add-btc-to-brick-money": [
      "sed -i '' \"s/return \\[/return \\[\\n    'BTC' => \\['BTC', 0, 'Bitcoin', 8\\],/\" vendor/brick/money/data/iso-currencies.php"
    ],

NB: 0 will cause problems if you're serializing & deserializing.

from money.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.