README.md 4.03 KB
Newer Older
Mathias Verraes's avatar
Mathias Verraes committed
1
2
3
Verraes\Money
=============

Mathias Verraes's avatar
Mathias Verraes committed
4
5
Also see http://blog.verraes.net/2011/04/fowler-money-pattern-in-php/

Mathias Verraes's avatar
Mathias Verraes committed
6
This is a PHP implementation of the Money pattern, as described in [Fowler2002]:
Mathias Verraes's avatar
Mathias Verraes committed
7
8
9
10
11
12
13
14
15
16

> A large proportion of the computers in this world manipulate money, so it's always puzzled me 
> that money isn't actually a first class data type in any mainstream programming language. The 
> lack of a type causes problems, the most obvious surrounding currencies. If all your calculations 
> are done in a single currency, this isn't a huge problem, but once you involve multiple currencies 
> you want to avoid adding your dollars to your yen without taking the currency differences into 
> account. The more subtle problem is with rounding. Monetary calculations are often rounded to the 
> smallest currency unit. When you do this it's easy to lose pennies (or your local equivalent) 
> because of rounding errors.

Mathias Verraes's avatar
Mathias Verraes committed
17
18
Examples
========
Mathias Verraes's avatar
Mathias Verraes committed
19

20
21
22
23
24
25
Creation
--------

All amounts are represented in the smallest unit (eg. cents), so USD 5.00 is written as

	<?php
Mathias Verraes's avatar
Mathias Verraes committed
26
	$fiver = new Money(500, new Currency('USD'));
27
28
29
30
	// or shorter:
	$fiver = Money::USD(500);
	 

Mathias Verraes's avatar
Mathias Verraes committed
31
32
Allocation
----------
Mathias Verraes's avatar
Mathias Verraes committed
33

Mathias Verraes's avatar
Mathias Verraes committed
34
35
36
37
38
My company made a whopping profit of 5 cents, which has to be divided amongst myself (70%) and my
investor (30%). Cents can't be divided, so I can't give 3.5 and 1.5 cents. If I round up, 
I get 4 cents, the investor gets 2, which means I need to conjure up an additional cent. Rounding 
down to 3 and 1 cent leaves me 1 cent. Apart from re-investing that cent in the company, the best solution 
is to keep handing out the remainder until all money is spent. In other words:
Mathias Verraes's avatar
Mathias Verraes committed
39

Mathias Verraes's avatar
Mathias Verraes committed
40
	<?php
41
	$profit = Money::EUR(5);
Mathias Verraes's avatar
Mathias Verraes committed
42
43
44
45
46
47
48
49
50
51
52
53
54
	list($my_cut, $investors_cut) = $profit->allocate(70, 30);
	// $my_cut is 4 cents, $investors_cut is 1 cent

	// The order is important:
	list($investors_cut, $my_cut) = $profit->allocate(30, 70);
	// $my_cut is 3 cents, $investors_cut is 2 cents

Immutability
------------

Jim and Hannah both want to buy a copy of book priced at EUR 25. 

	<?php
55
	$jim_price = $hannah_price = Money::EUR(2500);
Mathias Verraes's avatar
Mathias Verraes committed
56
57

Jim has a coupon for EUR 5.
Mathias Verraes's avatar
Mathias Verraes committed
58
	
Mathias Verraes's avatar
Mathias Verraes committed
59
	<?php
60
	$coupon = Money::EUR(500);
Mathias Verraes's avatar
Mathias Verraes committed
61
62
63
64
65
66
67
68
69
70
71
72
73
	$jim_price->subtract($coupon);

Because $jim_price and $hannah_price are the same object, you'd expect Hannah to now have the reduced
price as well. To prevent this problem, Money objects are immutable. With the code above, both 
$jim_price and $hannah_price are still EUR 25:

	<?php 
	$jim_price->equals($hannah_price); // true

The correct way of doing operations is:
	
	$jim_price = $jim_price->subtract($coupon);
	$jim_price->lessThan($hannah_price); // true
74
	$jim_price->equals(Money::EUR(2000)); // true
Mathias Verraes's avatar
Mathias Verraes committed
75
	
Mathias Verraes's avatar
Mathias Verraes committed
76

Mathias Verraes's avatar
Mathias Verraes committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90
The goal
========

Implement a reusable Money class in PHP, using all the best practices and taking care of all the
subtle intricacies of handling money. I hope to add a lot more features, such as dealing with major
units and subunits in currencies, currency conversion, string formatting and parsing, ...
Other ideas include integration with Doctrine2, which should make it easier to store money
in a database transparently. 

Status
------

At the moment, all the features that Fowler describes are implemented, which should make 
it usable for production code. Everything is unit tested (code coverage is close to 100%). 
Mathias Verraes's avatar
Mathias Verraes committed
91

Mathias Verraes's avatar
Mathias Verraes committed
92
93
94
95
96
97
98
99
100
101
Inspiration
===========

* https://github.com/RubyMoney/money
* http://css.dzone.com/books/practical-php-patterns/basic/practical-php-patterns-value
* http://www.codeproject.com/KB/recipes/MoneyTypeForCLR.aspx
* http://www.michaelbrumm.com/money.html
* http://stackoverflow.com/questions/1679292/proof-that-fowlers-money-allocation-algorithm-is-correct
* http://timeandmoney.sourceforge.net/
* https://github.com/lucamarrocco/timeandmoney/blob/master/lib/money.rb
Mathias Verraes's avatar
Mathias Verraes committed
102
* http://joda-money.sourceforge.net/
Mathias Verraes's avatar
Mathias Verraes committed
103
* http://en.wikipedia.org/wiki/Currency_pair
Mathias Verraes's avatar
cleanup    
Mathias Verraes committed
104
105
* https://github.com/RubyMoney/eu_central_bank
* http://en.wikipedia.org/wiki/ISO_4217
Mathias Verraes's avatar
Mathias Verraes committed
106
107
108
109
110
111
112
113

Bibliography
============

[Fowler2002]
Fowler, M., D. Rice, M. Foemmel, E. Hieatt, R. Mee, and R. Stafford, Patterns of Enterprise Application Architecture, Addison-Wesley, 2002.
http://martinfowler.com/books.html#eaa