Closed sagikazarmark closed 7 years ago
The second option does not make any sense. If you ask me: 1. use the value object directly, 2. implement a doctrine interface/extend doctrine.
Example: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/custom-mapping-types.html.
Doctrine type mapping is probably not an option since it allows you to have ONE column.
My concern is that option one includes too much Doctrine magic and reflection whereas in case of option too it's just scalars that it needs to take care of. I am not saying you are not right, just checking the options.
I like option 1 better. My projects implement it in a similar way. Currency is a custom type, not an embeddable, and I use a decimal field for the amount.
https://github.com/Padam87/AccountBundle/blob/master/Resources/Money/doctrine/Money.orm.xml https://github.com/Padam87/AccountBundle/blob/master/Doctrine/Type/CurrencyType.php https://github.com/Padam87/AccountBundle/blob/master/Doctrine/Type/MoneyType.php
Pros:
Cons:
@geekdevs The bundle takes a very different approach, any chance it would be able to adapt?
@Padam87 absolutely, developing something similar to Option1 is in plans for next major version of the bundle. If it's implemented within moneyphp/money - even better, we can reuse it.
one thing to remember about storing amount as string is that sorting might be painful:
1, 10, 2, 20, 3, 4
That is one of the reasons I'm using decimal in my projects, and why I would suggest Option 3, with the custom types + embeddables.
Sometimes (think of analytics) storing the "real" amount is necessary, and decimal allows to do that without the floating point issues...
Thanks for all the input here.
@geekdevs nice catch about the sorting issue. Using bigint as suggested by @frederikbosch might be an option.
I will try to play with it within a symfony bundle, but since it's a few lines of code (config), maybe we can ship it within this package?
This is the approach I am using in multiple projects: https://github.com/MichaelGooden/mdg-money-doctrine
Currency is put in as a custom doctrine type (which incidentally didn't suffer from the "BC break" in v3.0.0)
Money is then implemented as an Embeddable using bigint for the amount and the Currency custom type.
You can then do fun stuff like arithmetic in the database:
use Money\Currency;
use Money\Money;
public function getBalance(UserProxy $userProxy, Currency $currency) : Money
{
$qb = $this->repository->createQueryBuilder('t');
$qb->add('select', 'SUM(t.amount.amount)')
->andWhere('t.amount.currency = :currency')
->andWhere('t.userProxy = :user')
->setParameter('currency', $currency)
->setParameter('user', $userProxy);
$balance = $qb
->getQuery()
->getSingleScalarResult();
if (!is_numeric($balance)) {
$balance = 0;
}
return new Money($balance, $currency);
}
nice @MichaelGooden, that's very close to what i have.
Hm, haven't thought about this. It's definitely a pro then for the embeddable solution.
Prefer option 1 but already had some gotcha. Doctrine embeddable don't fallow value semantics. Money object can be changed from outside if it's an embeddable. Consider case:
$amount = $balance->getAmount();
$report->withAmount($amount);
$entityManager->flush($report);
// Some time later
$entityManager->refresh($balance) // Here report could be also modified, if somebody changed balance amount (Money).
$entityManager->flush(); // Here modified report will be persisted with overwrite of previous data.
And the workaround is like:
public function withAmount(Money $amount) {
$this->amount = clone $amount;
}
Shall we close this one? It is out of the scope of this library. Maybe inside the scope of this organization. But it should not be an open issue here. Right? I am closing, until someone comes up with a good reason to have it open here.
@frederikbosch what's the status on the Doctrine integration library? Can I help you guys with that? I'm contributor in Laravel Doctrine and I have multiple projects mapping Money to Doctrine already.
Cheers!
@guiwoda No idea. I am not using Doctrine. Maybe @sagikazarmark knows?
@guiwoda Contributions are certainly welcome. We had a few options listed, but unfortunately I didn't really have the time I wanted to spend on it.
A good first step would be doing some research based on this and the linked issues to see what options we considered, what the pros and cons are and decide based on that.
I am not against having multiple solutions supported as they could serve different purposes. Whether we want to ship an integration layer in this or in a separate package is up to discussion. I usually like keeping things separated.
So if you can find some time to pick up this issue that would be really great, many thanks in advance for the offer.
Alright, I'm on it!
You can see progress here: https://github.com/guiwoda/doctrine-money
Hey @sagikazarmark! I tagged you in an issue in the repo I linked in the comment above. I don't know if what I've done is enough to make it into a library or if you guys expect to have framework integration as well.
README explains the basic way to add mappings to Doctrine, so that should be enough for any usage. Anything else would be extra help for framework specific code (Laravel's ServiceProvider, for example.)
Any follow-ups @sagikazarmark?
Hi everybody,
Using bigints is mentioned a couple of times in this thread, and many others out there, as being a natural way to integrate Doctrine with money. However -- please correct me if wrong -- are not bigints inherintly unnecessary and risky?
Here are some facts:
I may be ignoring something, but I wonder why it is encouraged to use bigint when it turns out that it actually violates the KISS principle, increasing the surface of vulnerability of applications using it.
I think that in terms of simplicity, performance, scalability, and most importantly security, bigints should be used only when necessary; for example in astronomy apps. What is the point of using BIGINT as a rule of thumb? Keep in mind that the maximum BIGINT value is 18446744073709551615, and there's about $60 trillion on Earth -- which is $60,000,000,000,000.
For further information, please visit:
@programarivm As mentioned on the frontpage of this library: we are using strings because we want to support big integers! This lib includes BigMoney. So that basically makes all your arguments invalid.
Two remain:
@programarivm See https://github.com/moneyphp/money#features for all features that the lib is offering you. Apparently more than you think ;)
@programarivm Unfortunately none of your points are valid. All your linked resources are either irrelevant, referring to problems fixed in modern versions or talking about a bug in a specific adaptor for a minor DB engine (that has not even had a stable release in 3 years.)
This library intentionally uses string
for storing the integer, which ties in nicely with Doctrine. You can have a look at (one) way of doing it in my library
Your only vaguely relevant point is that machines running 32bit software are going to have a bad day. That said, if you are using some ancient 20 year old 32bit only server processor to handle millions in financials, you must be a bank. God help you.
That said, if you are using some ancient 20 year old 32bit only server processor to handle millions in financials, you must be a bank. God help you.
Haha :+1:
Guys, you've been pretty quick to correct @programarivm in his bigint assertion, but I'm still waiting for your feedback on the Doctrine integration package!
@guiwoda Since I do not use Doctrine, I am not the person to provide you with the feedback. I know Mark is on holidays at this moment. So it will have to wait. Maybe give him a @ mention in two weeks or so.
@guiwoda I use my own :D https://github.com/michaelgooden/mdg-money-doctrine
I think this issue is too complex to have an universal solution. Embeddable seems like the way to go for the value object, but the field mapping is opinionated, no matter what you choose.
Therefore I strongly suggest this:
<doctrine-mapping>
<embeddable name="Money\Money">
<field name="amount" type="money" />
<field name="currency" type="currency" />
</embeddable>
</doctrine-mapping>
The library can provide a default money type (which may be a string, bigint or decimal), and a currency type.
Let's say the lib provides a bigint type, and I would prefer a decimal for my system.
I could still use this lib with my custom decimal money type, I would just have to override the money
type in the doctrine config.
I don't understand, I made the embeddable implementation in literally 5 minutes. If embeddables don't fit in your implementation, it will probably take you another 5 minutes to do whatever you see fit. It's not about solving everyone's problems, it's about providing at least one solution!
I've been using Money and Doctrine for live projects, and I've never had any issue at all with the bigint implementation. Decimal doesn't make any sense unless you are actually converting from cents to decimal and back every time.
Can't we at least have one official doctrine package, then iterate over it for other alternative? I'm asking for concensus here, not perfection.
For you, decimal does not make sense, for me it does, there is probably someone who would prefer to use string... different use cases and opinions.
If you set it to bigint, you won't be able to override it, because doctrine won't allow you to change field types with overrides. If you create a custom type for it (you could even map it to bigint by default), it can be swapped out easily.
@Padam87 you don't need to override if you never actually add the wrong mapping at all. If bigint doesn't work for you, then don't add the provided mapping and that's it. You can still use the provided Currency mapping, tho.
I don't really care about your use case. Don't get me wrong, I'm not saying it's not valid. But, as it was pointed out earlier in this thread, this lib already made the choice to use strings and work with BigMoney. This is not about making the most flexible library out there with every possible money representation.
I believe an initial bigint representation, aligned with Money
s internal properties, is as straightforward as it gets. It allows for database math (which string doesn't) and I believe the entire planet's gross income combined doesn't overflow a bigint representing cents.
I'm still surprised that you'll find OK to use bigint arbitrarily without any good reason behind. My point is a general one, it is about sticking with a simplicity mindset when it comes to writing apps or using this or that library, because you want to control complexity in your apps.
To my understanding:
By the way, I found another issue about setting up Doctrine and Money for PHP:
On a different side note, what about BC Math and GMP vulnerabilities?
Please educate users by adding a disclaimer in the documentation: use bigints carefully, otherwise your app's vulnerability surface may increase for free.
@programarivm I find your vendetta most entertaining.
@programarivm If you find a vulnerability, please report it. Your suggestions are not helping at all. Because of a CVE in the past, that does not mean functionality should not be used. We are still using SQL, are we? How many CVEs can you find on that topic? Please bother other people if you do not have anything useful to say.
@jkobus back from holiday, I will try to find some time next week for it.
There are two possible options in my mind.
Option 1
Same as in #327.
Use the money object itself as an embeddable.
Pros:
Cons:
Option 2
Use a separate embeddable.
And the mapping:
Then in the entity:
Since VO equality is not checked based on identity, but value this solution is valid. The advantage is the flexibility gained (less doctrine embed magic), but I am not sure it worth the extra effort.
@pamil I would love to hear your opinion as well.