Closed solodkiy closed 4 years ago
Very good point. This method throws an exception because there's no way to tell whether 2 amounts in different currencies are equal, without an exchange rate; but your use case is valid as well, one might want to check whether amount and currency are equal.
We have at least 3 options here:
isEqualTo()
to always return false
when the currencies are different, instead of throwing an exceptionisEqualTo()
to control this behaviour (I'm not sure to like this)isEqualTo()
untouched, and add another method (name?)@Solodkiy Can you tell me a bit more about your use case: in what situation do you need to check that both the amount and currency are equal?
@jiripudil @jkuchar @VasekPurchart Do you have an opinion here?
I write system to analyse transactions from the bank. For it I need to compare transactions with each other. Transactions are equals if their amount, and other data equals too.
I used moneyphp/money before. This lib use first option: equals always return bool, compare can throw exception. https://github.com/moneyphp/money/blob/master/src/Money.php#L128 But for backward compatibility I suggest create new method named equals (i think this is the most common name for this operation) and deprecate current isEqualTo.
There's no way to tell whether 2 amounts in different currencies are equal, without an exchange rate
In Money object you don't need to think about exchange rate at all. You have special class MoneyComparator fort this task.
Small research about naming:
"function equals(" 367,814 code results https://github.com/search?l=PHP&p=6&q=%22function+equals%28%22&type=Code&utf8=%E2%9C%93
"function isequal(" 27,040 code results https://github.com/search?l=PHP&q=%22function+isequal%28%22&type=Code&utf8=%E2%9C%93
I know equals()
might be used more (probably mimicking Java), but I like isEqualTo
, which is consistent in naming and behaviour with isGreaterThan()
, isLessThan()
, etc.
So my worry with isEqualTo()
returning false
for different currencies is introducing an inconsistency.
At first glance I'd say that adding an equals()
method that behaves as you want might be the best solution, but I wouldn't deprecate isEqualTo()
in this case.
Still, I'm a bit worried about having two equal(s)
methods, which might be confusing for users.
What about another, distinctive name, like isSameAs()
?
Still, I'm a bit worried about having to equal(s) methods, which might be confusing for users. What about another, distinctive name, like isSameAs()?
I was thinking (but did not manage to reply yet) the same - two methods with similar names and vastly different behaviours would be really confusing. And I think that I came up with isSameAs()
as well.
Another acceptable name would be isIdenticalTo()
.
isSameAs()
is not bad, but I'm afraid that "same" could be confused with "same object reference" (just like assertSame()
in PHPUnit).
Bonus question: should this method check that the contexts are identical as well? I would say no, but then the naming is, once again, confusing.
I actually thought about a name that might be more suitable: matches()
:
https://github.com/brick/money/blob/master/src/AbstractMoney.php#L206-L220
Is everyone happy with this name, before I tag a release?
I still think it can confuse people. But it solve my problem.
To be honest I would not ever expect $money->isEqualTo() to do anything with currency conversion. When I would do that I would like to use special comparator which would compare money with more knowledge it has.
When I write it on paper it what feels natural to me:
20 CZK = 20 CZK βοΈ SAME
20 CZK = 20 EUR β NOT SAME
25 CZK ~ 1 EUR NOT SAME; ALMOST SAME
25 CZK = 1 EUR β NOT SAME (I would never compare money in different currencies for equality; they are always almost the same, but not exactly)
The thing here is that when comparing different currencies, environment (outside world) is stepping in and it is up to model how precise they want to compare them (you have have realtime forex provider, than it does not make much sense), they will be equal one time and few milliseconds later they will not.
isEqualTo()
should just compare value and currency, if same, TRUE, otherwise FALSE (I would accept also NULL for different currencies as incomparable)isIdenticalTo()
should compare value, currency and context and return only TRUE / FALSEI would think of equality as logical equality and identity as it works generally with value objects (identical when all fields are the same)
@jkuchar Are you suggesting that isEqualTo()
's current behaviour should be changed to return false
instead of throwing an exception on different currencies?
I'm not really against the idea, but I have two remarks:
isGreaterThan()
etc., which makes me a bit uncomfortable0.3
(I don't mind)isIdenticalTo() should compare value, currency and context and return only TRUE / FALSE
Unless someone has a valid use case, I currently don't see the point of adding a method that compares everything, including the context. This is not what's been asked here.
No feedback here? I'd like to move forward with this issue.
It would start behaving differently from isGreaterThan() etc., which makes me a bit uncomfortable
Me too, but I have only yet used this library in a single-currency environment, in which the current and proposed behaviour is essentially the same. Hence my view might be skewed, but I'm in favour of:
isEqualTo()
aligned with isGreatherThan()
in terms of throwing an exception for different currencies, because they are incomparable without providing exchange rates (and even then the intention to compare them should be expressed as explicitly as possible, e.g. via a special comparator); andmatches()
sounds good). It'd also make sense to me to suggest using this method in the exception message when comparing different currencies via isEqualTo()
. This might prevent some further confusion.But then again, I lack first-hand experience with multiple currencies. I'd love to hear from someone who's been using this library in a multi-currency setup.
I'm quite busy right now, I will get back to this this weekend.
I'm not experienced but will throw my 2 cents. AFAICS, I see two ways:
I favor the second option as it will not break compatibility and will make things easier and explicit. As to the wording, I think isSameAs
is quiet tempting. Because from application point of view having same value and currency means "same money". If that is not fitting well with the rest of community then am fine with is isIdenticalTo`
I am for:
isEqualTo
aligned with isGreatherThan()
isSameAs
is better than matches
, because matches sound much less strict, essentially like the mathematical ~
@jkuchar mentioned - so I would expect quite the opposite - that it tries to convert the currencies approximatellyisIdenticalTo()
sounds great for comparing all the values based only on state, but I agree we don't need to do that here@jkuchar Looking forward to your feedback ;)
@BenMorel what is the progress so far?
@mtangoo Waiting for more feedback... I don't have a strong opinion at the moment, and I would welcome some additional thoughts from you guys, in particular @jkuchar who's gonna get back to this after his week-end... :wink:
Ok. I think all have been said and all I have to do is wait too...!
To add some personal feedback:
isEqualTo()
similar to isGreaterThan()
.matchesValueOf()
. matches()
somewhat implies a value, so it'd be ok to keep it matches()
but the former clarifies intent.I agree that is makes sense, at least by default, to keep isEqualTo()
's behaviour aligned with all other comparison methods:
compareTo()
isGreaterThan()
isGreaterThanOrEqualTo()
isLessThan()
isLessThanOrEqualTo()
It would be very surprising to see isEqualTo()
return false
on different currencies, while isGreaterThanOrEqualTo()
throws. Their naming similarity should imply a similar behaviour, IMO.
It would be equally surprising to see isEqualTo()
behave differently from compareTo() === 0
, IMHO still.
It's also comforting to see that Java's MonetaryAmount.isEqualTo() does the same:
@throws MonetaryException if the amount's currency is not equals to the currency of this instance.
So let's summarize, once again, our options:
isEqualTo()
public function isEqualTo(bool $throwOnDifferentCurrencies = true) : bool
This way, it would behave by default as it currently does, but one would be able to ask it to return false
instead of throwing an exception when comparing different currencies.
We'd keep the current method as is, and add a new one. Suggested names so far (in their order of appearance in this thread):
equals()
isSameAs()
isIdenticalTo()
matches()
matchesValueOf()
Let me add a few more ideas to this list:
isSameValueAs()
isSameValueAndCurrencyAs()
isAmountAndCurrencyEqualTo()
isEqualToAndSameCurrencyAs()
If we really want to convey the meaning of the method in the name, maybe we should be this verbose?
Open question:
None of these options is a BC break, so they're equal on this point.
I, however, somehow dislike option 1: changing the throwing behaviour of a method with a parameter is a smell to me (I can't explain why, though, so feel free to prove me wrong on this point).
On the other hand, none of the names from option 2 found favour in my sight so far.
Maybe we should cast a vote, but we're not many here so I'm not sure what this is worth :)
@BenMorel Thanks for nice summary.
I'm really happy that his issue is still open, despite my long weekend (joke). I would like to add my two cents that come up to my mind while I was reading this whole thread again.
Let's dive into definitions a little.
Interesting is that part which says
wiki: inequality If the values in question are elements of an ordered set, such as the integers or the real numbers, they can be compared in size.
(I would consider Money object as pair of currency and decimal value)
This means - if we start with all values Money(currency, decimal value)
object can represent we will end up with unoredered (generic) set. If we define equivalency classes by grouping values by currency, we come up with ordered set
s for all possible values for each currency (one orderedset can be e.g. Money<CZK, decimal value>
. This also means that we have defiend equivalence classes where are all values for each currency. This subset must be always ordered set as it contains all values for once currency only and value is always a decimal number.
This implies that isEqualTo
(equality realation) is always defined for every two Money
objects. This is because it does not depend from which class of equivalence value come from.
However if we are comparing values (greater then, lower then, ...) we need to have both values coming from the same class of equivalence, otherwise greatherThen
and lowerThen
is not defined. This is typically implemented by returning neutral/unknown value (unit in Haskell, null in native-PHP or exception in Java).
As there is a fundamentail difference between equality and inequality operations, so it makes sense to me to respect nature of these operations so:
isEqualTo()
should be always defined - should return TRUE/FALSEisGreatherThen()
and isLowerThen()
should NOT be always defined - returns TRUE/FALSE/exceptionexception
al question...Should exception be checked (extending \RuntimeException
) or unchecked (extending \LogicException
)?
As it does not make sense to me to have try...catch block for every isGreatherThen()
and because isGreatherThen()
is just NOT defined on values with different values I would consider this as invalid usage.
Context
sAs Context
s do not affect value of Money
object directly, it affects behaviour of operations. It is not important for equality and inquality relations, therefor I haven't mentioned it in text above.
This also implies that I would consider 1 CZK
(cash) and 1.00 CZK
(digital transfers) the same and definitely comparable (greather, lower then)
Interesting point that equality and comparison is not the same, though I'm not sure most people would intuitively understand it that way without reading the docs. It would be acceptable to make isEqualTo only return true/false, and the others throw an exception.
I know this thread related to "equals" specifically, but this morning I started thinking that if isEqualTo
added a boolean value to also check currencies, undoubtedly someone in the future will create an issue asking why only isEqualTo
allows comparing different currencies, and not any of the other methods like isGreaterThan
. The same could be said of using a separate method to compare currencies, like isSameValueAs
-- someone would ask for a isValueGreaterThan
.
I, however, somehow dislike option 1: changing the throwing behaviour of a method with a parameter is a smell to me (I can't explain why, though, so feel free to prove me wrong on this point).
Agreed. Option two would make the most sense, in addition to other comparisons methods that do market price conversions (if you believe that is within the scope of this project--not sure you want to go that route.)
@jkuchar Thanks for your reply, I hope you enjoyed your weekend! π
You definitely pushed the discussion to the next level here.
Is comparison and equality the same thing? Therefore should they behave the same?
If I understand you correctly, the interesting part in the quote from Wikipedia is actually "It does not say that one is greater than the other, or even that they can be compared in size" (emphasis mine).
So your reasoning is that 1 USD and 1 EUR cannot be compared in size, but can still be compared for equality (not equal).
This reasoning definitely makes sense, however my worries from my previous post still hold; the most prominent one being that if you stop throwing exceptions on different currencies in isEqualTo()
, you behave differently from isGreaterThanOrEqualTo()
, despite the naming suggesting (to me, at least) that one operation is a subset of the other! I believe that this can be really confusing for consumers of the API.
Also, when I think about the way I use Money in my own applications, most of the time I do want an exception if I accidentally compare two monies with different currencies for equality! So even though your suggestion would solve the op's use case, it would leave me without a method for my use case, so back to square one, we'd still need another method or a boolean parameter!
In summary, I don't think changing the behaviour of isEqualTo()
alone, is a good solution.
One more exceptional question...
You're most probably right about exception management. An unchecked exception would make sense when comparing monies with different currencies, as this likely represents an error in your program, and less likely represents an exception you'd catch in a normal program flow.
This is a topic of its own though, especially with the lack of definition of checked and unchecked exceptions in PHP, therefore I encourage you to open another issue if you want to discuss this further.
Sidenote on Contexts
Fully agree that we shouldn't be comparing the context; I had to ask the question though.
@eblanshey I appreciate your feedback as well!
undoubtedly someone in the future will create an issue asking why only isEqualTo allows comparing different currencies, and not any of the other methods like isGreaterThan.
I don't think someone could argue that for inequality methods, as you have no way to say which one is greather than the other, without currency conversion, which is outside the scope of the Money object itself.
Option two would make the most sense
I still believe that indeed, even though @jkuchar's comment made me think about it for a little while.
in addition to other comparisons methods that do market price conversions (if you believe that is within the scope of this project--not sure you want to go that route.)
It's definitely in the scope of this project, and is already there, see MoneyComparator!
The more I think about it, the more I like the original suggestion of isSameAs()
. It conveys the meaning quite well, with just 2 drawbacks:
===
)Sigh...
isSameValueAs()
would do, provided that by "value" we understand the (amount, currency) pair: is that an appropriate word?
Maybe something like IsAmountAndCurrencySame? I still think that two similar named methods will be confusing, and we need maximize difference if we want to take this path. Value is not that obvious for my opinion.
Also I think that the best decision is have one equals method, without exeption. Agree with @jkuchar. Bool flags is not great too.
IsAmountAndCurrencySame?
isSameAmountAndCurrencyAs()
would be acceptable, I guess.
Also I think that the best decision is have one equals method, without exeption.
As I said above, my own expectation in my apps is to get an exception if I accidentally compare two monies with different currencies (my use case is probably very different from yours), so we need a method for that, too. What would you suggest then?
Can you tell more about cases when you need this compare? Do you catch this exceptions? What do you do in catch block? What will happen with your app if method reurn false without exeption?
Maybe assertion will cover your case? $m->assertCurrencyEqual(Money $m2) : void | exception You can place this kind of assertion just bellow code where you getting this objects and get an exception even faster.
Strange thought: we can resolve logic conflict between equals and great/less methods if we remove great/less from money class and left it only in MoneyComporator. (In this case we need something like SimpleExchangeRateProvider which compare only similar currencies and throw exceptions otherwise)
Sorry, I misunderstood the issue here. In addition to the isEqualTo method comparing currencies I thought OP also wanted to be able to compare the values using exchange rates Β―_(γ)_/Β― Don't ask how I derived that.
Considering just comparing if currency and value are the same, I'm personally indifferent as long as it is well documented.. Everyone else has their own intuitive idea of how it should work and you can't please everyone :)
Can you tell more about cases when you need this compare? Do you catch this exceptions?
I never catch these exceptions. When I do isEqualTo()
/isGreater|LessThan()
, I assume that I'm dealing with monies in the same currency. If by mistake, my code allows a money with a different currency to be compared, this means that I made a mistake somewhere in my code. One reason could be that I forgot to perform a currency conversion to bring all my monies in the same currency. Or that I didn't check the currency from a Money coming from an external input. In any case, I would not want isEqualTo()
to just return false
, I need it to throw, so that my script fails and I get a notification that something needs to be fixed.
Maybe assertion will cover your case? $m->assertCurrencyEqual(Money $m2)
I wanted to suggest the same thing for your use case:
if ($money->isSameCurrencyAs($other) && $money->isEqualTo($other)) { ...
This would also solve your problem, but I didn't suggest it because I thought that calling two methods when one could do would make the code unnecessarily verbose. So the same goes for your suggestion: I don't want to pollute my code with additional assertCurrencyEquals()
calls, when the comparison method can do the check for me transparently.
Strange thought: we can resolve logic conflict between equals and great/less methods if we remove great/less from money class and left it only in MoneyComporator.
I don't like this idea at all: when your app mostly deals with monies in a single currency; this adds a level of complexity that you really don't need!
Considering just comparing if currency and value are the same, I'm personally indifferent as long as it is well documented.. Everyone else has their own intuitive idea of how it should work and you can't please everyone :)
Sure, as long as it's documented it's fine. It looks like the only way to please everyone here, naming conventions aside, is to create two methods.
Let's just find a name that is meaningful enough to keep confusion to a minimum?
@BenMorel I think about the way I use Money in my own applications, most of the time I do want an exception if I accidentally compare two monies with different currencies for equality! So even though your suggestion would solve the op's use case, it would leave me without a method for my use case, so back to square one, we'd still need another method or a boolean parameter!
I do not like this idea as you are bringing your domain-specific usage into general-purpose library. So Iβm still for strictly distinguishing between comparision and equality. (see general equals method later in this post)
Values from different classes of equivalence as simply un-equal-able without addition knowledge (currency convertor). So I would be strict on Money object and provide whole new API for manipulating Money objects multi currency environment. (but do not want to discuss it here as it can bring lot of complexity here as converting currency pairs in different directions does not have to lead into the same result as spread exists in the market and it can depend on amount size and hevily on time and this breaks consistency rule of java equals).
Β @Solodkiy IsAmountAndCurrencySameAs()
It is descriptive, but I do not like this as it just describes implementation not the domain concept. It is qualitatively the same thing as
$100czk->getAmount()->equals($5eur->getAmount()) && $100czk->getCurrency()->equals($5eur->getCurrency())
@BenMorel In any case, I would not want isEqualTo() to just return false, I need it to throw, so that my script fails and I get a notification that something needs to be fixed.
I still do not get what kind of usage that is. Please, could you elaborate on this more? (maybe I have partly answered this question later)
As there is still not clear attitude what outcome should be - Iβm going to evaluate on Money API more.
I will work with there two objects:
$czk100 = Money::of(100, Currency::ofCountry('czk'));
$eur5 = Money::of(5, Currency::ofCountry('czk'));
These methods does not allow cross-currency operations:
$czk100->plus($eur5); // : β usage exception
$czk100->minus($eur5); // : β usage exception
$czk100->multipliedBy($eur5); // : β usage exception
$czk100->dividedBy($eur5); // : β usage exception
$czk100->isGreaterThan($eur5); // : β usage exception
$czk100->isLessThan($eur5); // : β usage exception
$czk100->isGreaterThanOrEqualTo($eur5); // : β usage exception
$czk100->isLessThanOrEqualTo($eur5); // : β usage exception
And equals is different:
$czk100->equals($czk100); // true
$czk100->equals($eur5); // false
$czk100->equals('hello!'); // false
$czk100->equals(new \DateTimeImmutable()); // false
from Java docs
Indicates whether some other object is "equal to" this one.
The equals method implements an equivalence relation on non-null object references:
- It is reflexive: for any non-null reference value x, x.equals(x) should return true.
- It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
- For any non-null reference value x, x.equals(null) should return false.
The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).
Parameters:
obj - the reference object with which to compare.
Returns:
true if this object is the same as the obj argument; false otherwise.
According to Java docs: There is no possibility of throwing exception as all objects are sort-of equal-able. For example Date(2018-11-27) is not equal to Money(100, CZK). And I definitely cannot say that Date(2018-11-27) > Money(100, CZK)
isEqualTo()
domain-specific or general method?There are basically two valid point of view on this.
Take Java equals()
method in general: everything is comparable, so it will assign for every two objects if they are equal or not. This is cool as it can be used accross the system and will still hold the same interface. According to Java docs there exists no objects that we do not know if they are equal or not - therefore equals should not throw an exception.
Problem is that there is no standard-way of doing this in PHP.
Also read this stack overflow summary. Because every object MUST be equal or not equal to each other, that is why Java has equals method on Object
class.
Same usage as equals in β΄. Difference is that is defined only for two Money
objects with the same currency. There are now three results - equal, not equal and incomparable. This is quite close like if we would have separate class for each currency and have generics for that e.g. Money<CZK>
. That would feel much more natural - as generic parameters could create βnewβ object type for each currency.
If someone accidentally uses this method for collections it can do strange things. E.g. when Money is as key and someone is checking if the key is equal to key which is already saved. One get and exception. Unexpected. As there is no standard way of defining equals in PHP I use my helper for that.
@jkuchar Thanks for your detailed post, lots of interesting insight, in particular Java's definition of equals()
.
I do not like this idea as you are bringing your domain-specific usage into general-purpose library.
That's not my intention. I was attempting to explain that, even though the current behaviour of isEqualTo()
does not make sense in the OP's domain, it does to some extent in mine, so we need to find a solution that's good for everyone, which is the whole intent of a general-purpose library.
Values from different classes of equivalence as simply un-equal-able without addition knowledge (currency convertor). So I would be strict on Money object (...)
That's exactly what the library does at the moment: throwing exception when comparing different currencies, whether this is for inequality or equality.
(...) and provide whole new API for manipulating Money objects multi currency environment.
Did you see above that we already have a MoneyComparator implementation?
In any case, I would not want isEqualTo() to just return false, I need it to throw, so that my script fails and I get a notification that something needs to be fixed.
I still do not get what kind of usage that is. Please, could you elaborate on this more?
Simple: some parts of my code deal with multi-currencies, and some other parts deal with the assumption that all monies are of the same currency. If this assumption is broken by a defect in my code, leading to a leak of a Money with a different currency, I'd like to get an exception when I perform an isEqualTo()
, instead of just getting false
and letting the defect potentially go unnoticed.
- General equals method
A similar suggestion has been made in brick/date-time#9, with no feedback from the OP so far. I do not quite like this approach. In Java this makes sense, as this is a language requirement that all objects must be comparable to each other. In a specific PHP library however, I don't see any value in providing a generic equals()
method where you could, to take an extreme example, compare a Money
to a LocalDate
.
I like to build libraries around use cases: what's a legitimate use case for such a comparison? If there is none, then this "feature" should not be built into the library IMO.
- Money-specific equals method There are now three results - equal, not equal and incomparable.
Exactly, and we have so far 2 equally valid use cases, that only differ in how they handle equality on 2 monies with different currencies:
false
;throw
an exception.I still think that isEqualTo()
's behaviour should stay aligned with that of other is*()
methods, and stay conceptually equivalent to compareTo() === 0
, so that:
isEqualTo($x)
is the same as compareTo($x) === 0
isGreaterThan($x)
is the same as compareTo($x) > 0
isGreaterThanOrEqualTo($x)
is the same as compareTo($x) >= 0
I would not break this consistency, but I'd rather add a new method for the OP's use case, method for which we don't seem to agree on a good name.
What do you suggest, exactly, @jkuchar?
I haven't been suggesting anything in particular. I have just been trying to put all arguments that I had on my mind. (yep, sometime contradicting each other π) I just ended with that there are really two use-cases.
Β > (...) and provide whole new API for manipulating Money objects multi currency environment. Did you see above that we already have a MoneyComparator implementation?
Yep, it totaly makes sense to make these copariosons external as they are more conserned with coverting then comparing values.
After thinking again about this I agree that:
isEqualTo()
(throwing exception) makes senseFor me it would makes sense to implement \Ds\Hashable
from phpds
(polyfill available if ext not available). They are value objects therefore it perfectly makes sense to use them in collections. Implementhing equal
and hashCode
from \Hashable
allows such usage.
Implementing comparisons for collections is yet-another-use-case I think, as now it's questionable whether you should compare contexts as well?
I don't have a strong opinion, but to add an option to the discussion: if you want to go the "two separate methods" route but don't want to be verbose, you can use PHPunit's method: it has "assertEquals" and "assertSame" for strict checking. Perhaps keep "isEqualTo" as it is, and make "isSameAs" always return true or false.
I've already suggested isSameAs()
, but raised doubts regarding the possible confusion with the ===
operator (which is PHPUnit's meaning for same), and the fact that it doesn't compare contexts, only amount and currency.
looks like discussion is't moving to conclusion...
I guess pretty much everything has been said: we just need to find a reasonable name for the new method. I've listed some ideas above. Suggestions / votes welcome.
Suggestions / votes welcome.
Mine goes to isAmountAndCurrencyEqualTo()
My ordered list of favorites:
is()
- $czk100->is($eur5) === false
$czk100->is(anything excluding Money) === false
equals()
- $czk100->equals($eur5) === false
equals($other)
\Ds\Hashable
isAmountAndCurrencyEqualTo()
- $czk100->isAmountAndCurrencyEqualTo($eur5) === false
Any other votes out there, @Solodkiy, @VasekPurchart, @jiripudil, @eblanshey?
While isAmountAndCurrencyEqualTo()
is on the verbose side, it communicates its intent without leaving much space for ambiguity, so :+1: for it from me
I tend to lean towards isAmountAndCurrencyEqualTo()
, too. Let's wait a couple days to give others a chance to give their opinion, and we'll move forward.
Is Money
considered a VO? If so, I would expect an equals() method to compare equivalence of the values represented by the VO (e.g, Amount
and Currency
). Such is done by another money library cited earlier in this thread.
If considered a VO, the context
property could be indicative of code smell. However, I've seen a context object used elsewhere, such as in Python's Decimal/Fixed Point Arithmetic module.
I'm not sure whether our Money objects can be considered VO as per Fowler's definition. They are immutable, don't have an identity, and can be compared by value, but comparison should exclude context indeed, so they probably don't qualify as VO.
We can't really compare brick/money with moneyphp, as the latter does not support, AFAIK, custom scales, cash roundings, etc., so they don't need a context at all, and more naturally qualify as VO.
We can't just have an equals()
method in addition to isEqualTo()
as it's too close and would be confusing. Suggestion has already been made above to change isEqualTo()
to return false
when currencies are different, but I'm not very comfortable with this idea for the reasons I already exposed above.
I have the same feeling regarding moneyphp's API: their inequality methods (lessThan()
, greaterThan()
, ...) delegate to compare()
and throw on different currencies, while the equality method equals()
does not throw on different currencies, just returns false
. It therefore behaves differently from compare() === 0
. Is that a good or a bad thing? I honestly don't know.
OK let's do a real poll on 3 options, as I don't want to introduce a bias by asking to vote on a new method name only.
I'd like everyone here to vote please. If you don't care, post "I don't care".
isEqualTo()
's behaviour to always return false
on different currencies (just like moneyphp's equals()
)isEqualTo(bool $throwOnDifferentCurrencies
) to control whether to throw an exception or return false
isEqualTo()
unchanged, and add a new method that returns false
on different currenciesYou can vote for just 1, 2 or all 3 options, in your order of preference. If you vote for option 3, add one or more method names in your order of preference.
Vote template:
- 1st choice: option x
- 2nd choice: option y (optional)
- 3rd choice: option z (optional)
Note that I'm not planning to rename isEqualTo()
to equals()
in any case, as I want to keep the same conventions throughout the project.
I need a simple bool method what answer me are the two money object completely equal (amount and currency) or not. isEqualTo looks like this method, but it throws MoneyMismatchException exception if currencies are different. What I should use for this problem?