Best Practice for Working with Currency Values in PHP

Best practice for working with currency values in PHP?

Let me answer this myself. Best practice would be to create a "money amount" class. This class stores the amount internally as cents in integers, and offers getters, setters, math functions like add, subtract and compare. Much cleaner.

Writing the good format of a currency in PHP

Yep, by the intl module.

For currencies there is a NumberFormater class:

http://www.php.net/manual/en/numberformatter.formatcurrency.php

How to handle and/or store money values in web applications?

What I would recommend, is to use :

  • decimal Euro values (1000.50) in your database

  • cents (100050) in calculations

  • a formatted string (1.000,50€) in your display to the user



More details

For storing a field in a MySQL database, something like DECIMAL(12,2) makes perfect sense. This is a numeric value that has the correct percision (two decimal digits) and allows you to store any monetary value up to 9999999999.99.

Cents are great for calculations, because it allows you to treat your monetary values as integers in calculations, which is more practical to work with (especially for divisions and multiplications), and which is typically the level you want your monetary values to be rounded up or down to.

No matter how you store your data in the database or how you do your calculations, you want your users to see the amounts in a format they're familiar with. For example, US dollars would usually formatted as $1,000.50 and Euros as 1.000,50€.

PHP/MySQL: Best money operations/storing practices?

I'd definitely go for using ints and routing everything through a data object (ORM) style that then handles all conversion for you. The client code using the data object will never need to do conversion and won't care, while you won't have problems with storage as ints are handled easily by the DB. Furthermore, you can then add whatever other methods are needed for the money object (like conversions between money types, etc) quite easily.

Best practice to sum and format numbers to dollar values with 2 decimal points?

To format a number to en_US currency format you can create a NumberFormatter object like this:

$oNumberFormatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY);

In this instance prices will be formatted up to normal rules for the given locale; for instance $9.95 for 'en_US` or € 9,95 for 'nl_NL'


If you want a specific ICU Decimal format you could use:

$oNumberFormatter = new NumberFormatter('en_US', NumberFormatter::PATTERN_DECIMAL, '¤ #,##0.00');

The currency symbol (¤) in the format will automatically be converted, when echoed out, to the correct symbol specified by the locale string; so for 'en_US' the dollar symbol ($) will be used - for 'nl_NL' the Euro () and so on.


Either way, in your product display loop, to display the correctly formatted product price, you need only (assuming your product price here is $price) use:

<?= $oNumberFormatter->format( (float) $price); ?>

... and for your free shipping insert:

<?= !$shipping ? "<span class=\"free\">Free Shipping</span>" : $oNumberFormatter->format( (float) $shipping ); ?>

Full details on the NumberFormatter class are at: http://php.net/manual/en/class.numberformatter.php


Just an edit to answer the question in the comments

Ok, thanks. can you explain what you did with the shipping part? I
didn't understand how that format is working. also, how to insert a
dollar sign if for the time being I will use Free Shipping" : number_format((float)$shipping,
2, '.', ''); ?> ?

<?= is shorthand for <?php echo

I'm just using a ternary operator: http://php.net/manual/en/language.operators.comparison.php (scroll down the page a little) - it's just a sort of shorthand for directly assigning a value based on a sort of if (?) else (:) notation:

!$shipping equates to $shipping == false which equates to $shipping == 0 since PHP is loosely typed, so:

<?= !$shipping ? "<span class=\"free\">Free Shipping</span>" : $oNumberFormatter->format( (float) $shipping ); ?>

... is equivalent to ...

<?php
if($shipping == 0) {
echo "<span class=\"free\">Free Shipping</span>";
}
else {
echo $oNumberFormatter->format( (float) $shipping );
}
?>

And for literally outputting a $ string... just use a non-interpolated string literal. PHP has 2 types of string literal (well, 4 with heredoc syntaxes) but essentially an interpolated string is enclosed in double quotes and will parse variables.

<?php
$sWord = "BADGERS";
echo "I like $sWord";
?>

Results in I like BADGERS

Whereas a non-interpolated string (enclosed in apostrophes) will treat $ literally - it won't interpolate variables:

<?php
$sWord = "BADGERS";
echo 'I like $sWord';
?>

Results in I like $sWord literally.

So you can echo out your currency value, with a prefixed dollar sign, like so:

echo '$' . number_format( (float) $shipping, 2, '.', '');

best practice to store mixed type of numbers (percents and flat values)

This might seems a little bit overkill, but I would definitely go for objects here.

Two classes, one that offset by an absolute value, the other by percentage value. Define a factory method to parse the input string and instantiate either class.

Then, have both classes extends a common interface, which will contains a method like getAjustedValue(initialValue):ajustedValue. This will make usage of these ajustment values much easier and cleaner.

Updated after OP comment

As for storage in the database, I would store both type of ajustements in distinct columns. It make it very easy to write a SELECT statement that compute the ajusted value, as in: SELECT (original_value * ajustement_ratio + ajustement_absolute) as adjusted_value FROM ....

Obviously, I expect that ajustements and original_value wouldn't come from the same table, but that give you the big picture.

How to handle precision when dealing with currency in MySQL and PHP

Round the stored values to the nearest penny. When you're calculating a total or subtotal using PHP, sum up the rounded values.

If you're going to be displaying rounded aggregates a certain way, it's probably best practice to calculate it the exact same way to avoid confusion.

You could display the aggregate items as not being rounded, and then round the final sum for display purposes. This may play more nicely in the long run if you're storing fractional pennies in the database.

Another option could be having a rounding charge on the invoice to account for the discrepancy, though that may confuse some people.

That being said, programmatically speaking, it's best to avoid fractional pennies whenever possible.

Whatever you choose to do, be consistent!

Best Practice to go with Defining Column when dealing with cash transactions

For real world currency transaction applications DOUBLE, REAL and FLOAT are not a good choice since they are not loss less when adding and subtracting values. You'll need exact precision.

Now, exact precision types such as INT, BIGINT, SMALLINT, or TINYINT are also not suitable since they don't have decimal places to record cents or pennies.

You are left with the type DECIMAL (also known as NUMERIC). Usually you'll want 2 decimal places for cents, and 12 for the integer part at least. Therefore, for a real world app I would go with DECIMAL(14, 2).



Related Topics



Leave a reply



Submit