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.