After lot’s of people spent a lot of time at work refactoring bad/old code after Alistair Darling announced that VAT would be reduced to 15% instead of 17.5% (and that people needed to ‘contact their software vendors to update the VAT field… riiight), I thought i’d stub out my own design for handling Price and Tax (whether it’s VAT or not shouldn’t really matter). Here’s a start. It follows the idea that where possible, a class should be immutable. Don’t modify an existing cost, create a new one and return that. Anyway – here’s the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Price
{
	protected $value = 0;
 
	protected $currency = null;
 
	public function __construct($value, $currency = 'GBP')
	{
		$this->value = $value;
		$this->currency = $currency;
	}
 
	public function getValue()
	{
		return $this->value;
	}
 
	public function getCurrency()
	{
		return $this->currency;
	}
 
	public function __toString()
	{
		return "{$this->value}";
	}
}

That’s all you should need for the price. Think about the money in your pocket – it has two property’s. It has a value, and it has a currency. I cannot change either without spending it (or converting it at a bureau de change), but then I would get a set of new coins in return. It has no concept of whether or not it is inclusive or exclusive of tax. Thats up to something else…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TaxMan
{
	protected static $vat = array(
		'GBP' => 15.0
	);
 
	protected $currency = '';
 
	public function __construct($currency)
	{
		if(array_key_exists($currency, self::$vat)) {
			$this->currency = $currency;
		} else {
			throw new Exception('Cannot build a TaxMan for this currency');
		}
	}
 
	public function getVat(Price $price)
	{
		return new Price(($price->getValue() * (self::$vat[$this->currency]) / 100), $this->currency);
	}
}

So we can pass in a price object and get a new value representing how much vat should be added… Something like this.

1
2
3
4
5
$price = new Price(8.00);
$taxman = new TaxMan('GBP');
$vat = $taxman->getVat($price);
 
$total = new Price($price->getValue() + $vat->getValue(), $price->getCurrency());

This is not completely finished however (and I may come back to it to complete it), as it’s completely possible for multiple different tax levels within a currency (Currency can be used in multiple countries, or different products have varying levels of VAT associated with them). This will solve some of the problem – but there’s more work to be done.