I am storing money in my database using an integer. This means that $0.50 is 50. I extended the Integer db field in such a way that this is now working correctly at the front end. It is nicely converted to and from integer.
In the backend, however I am having problems. The silverstripe CMS seems to do its own conversion (adding thousand separators for example), which has interesting results :).
How would you guys approach this problem? I tried to use an onbeforewrite and a custom getter.
This is the code I have, starting with an extension of the integer db-field
/**
* Format a number to currency
* @param int $number_of_decimals When larger than 0 it will return this number of decimals, AND divide the amount by 10^number of the amount of decimals
* @param bool $round Round the resulting number to the closest whole number
* @param string $thousands_char Character used as thousands separator
* @param string $decimal_char Character used as decimal separator
* @return string
*/
public function toCurrency($number_of_decimals=2, $round=false, $thousands_char=".", $decimal_char=",") {
$divide_by = pow(10,$number_of_decimals);
$value = $this->owner->value/$divide_by;
if($round) {
//no decimals when rounding :)
$number_of_decimals=0;
}
return number_format($value, $number_of_decimals, $decimal_char,$thousands_char);
}
public function fromCurrency($number_of_decimals=2, $thousands_char=".", $decimal_char=",") {
$multiply_by = pow(10,$number_of_decimals);
//get rid of the thousand separator
$value = str_replace($thousands_char,"",$this->owner->value);
//replace the decimal char with a point
$value = str_replace($decimal_char,".",$value);
$value = $value*$multiply_by;
return number_format($value, 0, ".","");
}
Also I added this to an extension of the SiteConfig (thus creating a sort of globally available function
/**
* Creates a DBField equivalent of the value, based on the type. In such a way, we can make use of the dame functions that are in an extension of a dbfield.
* @param $type The type of the DBfield to create (e.g. Varchar, Int etc.).
* @param $value The value, a string or number
* @return mixed
*/
public function ToDBField($type,$value) {
$field = $type::create();
$field->setValue($value);
return $field;
}
These functions do the actual work, and they are in a dataobject:
public function GetAmount() {
$amount = parent::getField("Amount");
if (is_subclass_of(Controller::curr(), "LeftAndMain")) {
$int_amount = SiteConfig::current_site_config()->ToDBField("Int", $amount);
return $int_amount->toCurrency($number_of_decimals=2, $round=false, $thousands_char="", $decimal_char=".");
}
return $amount;
}
public function onBeforeWrite() {
$int_amount = SiteConfig::current_site_config()->ToDBField("Int", $this->Amount);
$this->Amount = $int_amount->fromCurrency(2,",",".");
parent::onBeforeWrite();
}
So the problem you're facing is one of the default scaffolding based on the DbField
type. By default, type Int
will use a NumericField
in the CMS which does no currency formatting.
We can override the scaffolding for a form field in the getCMSFields
function of a DataObject
. Specifically using FieldList::replaceField
to replace the form field with one of our choosing.
function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->replaceField('Amount', CurrencyField::create('Amount', 'Amount'));
return $fields;
}
You have a number of options for this from building your own from scratch, building on-top of an existing form field or using a pre-existing one. CurrencyField
and MoneyField
both should do what you want without you needing to have your own functions exposed on the DataObject
to do the conversion back and forth.
There are a few things to note:
CurrencyField
is limited to US-centric formatsCurrencyField
and MoneyField
are not designed to be backed by an integerAt this point, I would consider altering your approach. MoneyField
seems to describe most-closely to what you want with the ability to more easily handle different currencies. The drawback is that it requires a Money
database field.
I've never used the MoneyField
though I believe for a few projects I have used the CurrencyField
. Because of that, I can't speak for how easy it is to work with and tweak to the way you want.
If you still would rather not take this approach, I would look at extending perhaps the CurrencyField
class (by this I mean Object-Orientated extending a class, not the built-in extension system) and tweaking that to your liking. Implementing that however is likely too broad to cover in this question.