1
votes

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();
    }
1
Can we see what your DataObject looks like? I'm curious as to why you are using an integer DB field where you can instead use a Currency field. This plus a few other tweaks to your code from the sound of it should help you get what you want working.Turnerj
Well, basically because of precision and the fact that some currencies do not have decimals, and others do. I wasn't aware of the currency field, by the way.jberculo
For your scenario then, how about extending a DBField (either Integer or Currency)? Extending it should allow you to better handle multiple currencies (because neither field really would be a perfect fit otherwise) and potentially extend one of the FormFields to best render your custom DBField in the CMS. With that said you don't mention what FormField element the CMS is currently using, NumericField or CurrencyField? Can we see some of the code relating to how you currently have this setup?Turnerj
I added some code. In the CMS I just used the scaffolding that is available for an integer field. Maybe I should convert that to a varchar field.jberculo
First thing I want to mention is your SiteConfig extension, it sounds like you pretty built/much wanted to use DbField::create_field. For your application, are you wanting to have a number field in the CMS to change the integer value or a currency-formatted string that converts to an integer?Turnerj

1 Answers

2
votes

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:

  • From the documentation, CurrencyField is limited to US-centric formats
  • CurrencyField and MoneyField are not designed to be backed by an integer

At 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.