Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Patterns: Money

DZone's Guide to

Practical PHP Patterns: Money

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

The Money base pattern is essentially a class whose objects represent a monetary value, expressed in some explicit or implicit currency.

While loosely typed variables and primitive types are often enough powerful to represent the simplest domain concepts, like phone numbers and addresses, programmers very careful when talking about money and the introduction of monetary amounts as first-class citizen can enforce business rules in the whole client code that uses the Money class.

In general, object modelling enforce constraints and make sure everything goes smoothly by encapsulating data and exposing higher-level controls. At this level of detail, the Value Object pattern is widely diffused in the Domain Model. In fact, the Money pattern is a specialization of the Value Object one.

Money operations

There are many issues that a Money class or component could tackle:

  • first of all, addition and subtraction of amounts, which is by far the most common operation over Money objects.
  • the class often handles amounts expressed in different currencies, like dollars and euros. The basic operations - addition and subtraction - are then extended for the use case of multiple currencies, and the conversion between currencies must be managed as well.
  • rounding of values after algebraic operations (like the calculation of a 20% tax) is also a complex use case, as there are different rounding policies used worldwide and even in the same country.

 

Rounding can be both voluntary (I prescribe to persist Money amounts with a 10^-4 precision but to show only the hundreths) and involuntary, due to the imprecision of floating point values.

Float vs. decimal

Basically, floating point types like float and double are prone to the introduction of precision errors, due to their fixed representation of decimals. This code:

<?php
echo floor((0.1+0.7)*10); // outputs 7. Try it

is an example of how relying on floating point values for monetary amounts can be misleading and tricky. The internal representation of the value passed to floor() would be something like 0.799998699999. At the same time, floats are very handy for quick calculations, like displaying the percentages of votes in a poll result.

Fortunately, decimal types are supported by all production databases you are likely to use. Decimal types have an arbitrary precision: you set up a decimal(10, 2) field and it will happily persist all your money amounts.

PHP uses floats and doubles by defaults in its operations (for example if you divide two integers). Of course, this leads to problems with equality comparison and arithmetic operations, as shown earlier.

The bc extension has various functions of arbitrary precision that can be used here, and that treat numbers as strings:

 string bcadd  ( string $left_operand  , string $right_operand  [, int $scale  ] )

A string can be arbitrary long, and can be used for storage of a decimal value if you can accept the overhead of conversion when needed. Another option is to use two integer fields for the integral and decimal part of a number, but then you will be on your own for implementing arithmetic operations.

Examples

In this sample, I wanted to use Zend_Currency, a component of Zend Framework 1. However, it uses floats, to my dismay. Zend_Currency has very valid features like the displaying of amounts in different currencies with patterns derived from the current locale.

We'll build a small component for arithmetic operations instead, the part that I don't trust Zend_Currency to do. It is a specialized Value Object: immutable and encapsulating a segregated value. A bit of logic is contained too - necessary for the arithmetic operations.

If you have more information on Zend_Currency and how it can implement a Money pattern, feel free to add your comments.

<?php
class MoneyTest extends PHPUnit_Framework_TestCase
{
    public function testMoneyRepresentationAsAStringIncludesDecimals()
    {
        $money = new Money('10.00');
        $this->assertEquals('10.00', (string) $money);
    }

    public function testMoneyAmountsCanBeAddedTogether()
    {
        $money = new Money('10.45');
        $finalMoney = $money->add(new Money('21.55'));
        $this->assertEquals('32.00', (string) $finalMoney);
    }

    public function testMoneyCanBeMultipliedForAFactor()
    {
        $money = new Money('54.46');
        $finalMoney = $money->multiply(100);
        $this->assertEquals('5446.00', (string) $finalMoney);
    }

    /**
     * This forces to use bc_*() functions.
     * Of course we probably don't need such large numbers for Money,
     * but arbitrary precision reflects on the single hundreths.
     */
    public function testMoneyCanBeMultipliedForAFactorAndMaintainPrecision()
    {
        $money = new Money('54.46');
        $finalMoney = $money->multiply('100000000000000000');
        $this->assertEquals('5446000000000000000.00', (string) $finalMoney);
    }
}

class Money
{
    private $amount;
    const SCALE = 2;

    /**
     * @param string
     */
    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function add(Money $another)
    {
        return new Money(bcadd($this->amount, $another->amount, self::SCALE));
    }

    public function multiply($factor)
    {
        $factor = (string) $factor;
        return new Money(bcmul($this->amount, $factor, self::SCALE));
    }

    public function __toString()
    {
        return $this->amount;
    }
}

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}