Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like GSTTaxModifier often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use GSTTaxModifier, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 13 | class GSTTaxModifier extends OrderModifier |
||
|
|
|||
| 14 | { |
||
| 15 | |||
| 16 | /** |
||
| 17 | * |
||
| 18 | * @var Boolean |
||
| 19 | */ |
||
| 20 | private static $show_in_cart_table = true; |
||
| 21 | |||
| 22 | /** |
||
| 23 | * message explaining how GST is based on a sale |
||
| 24 | * to a particular country ... |
||
| 25 | * @var string |
||
| 26 | */ |
||
| 27 | private static $based_on_country_note = ""; |
||
| 28 | // ######################################## *** model defining static variables (e.g. $db, $has_one) |
||
| 29 | |||
| 30 | /** |
||
| 31 | * standard SS variable |
||
| 32 | * |
||
| 33 | * @var Array |
||
| 34 | */ |
||
| 35 | private static $db = array( |
||
| 36 | 'DefaultCountry' => 'Varchar(3)', |
||
| 37 | 'Country' => 'Varchar(3)', |
||
| 38 | 'DefaultRate' => 'Double', |
||
| 39 | 'CurrentRate' => 'Double', |
||
| 40 | 'TaxType' => "Enum('Exclusive, Inclusive','Inclusive')", |
||
| 41 | 'DebugString' => 'HTMLText', |
||
| 42 | 'RawTableValue' => 'Currency' |
||
| 43 | ); |
||
| 44 | |||
| 45 | private static $many_many = array( |
||
| 46 | "GSTTaxModifierOptions" => "GSTTaxModifierOptions" |
||
| 47 | ); |
||
| 48 | |||
| 49 | /** |
||
| 50 | * standard SS variable |
||
| 51 | * @var String |
||
| 52 | */ |
||
| 53 | private static $singular_name = "Tax Charge"; |
||
| 54 | public function i18n_singular_name() |
||
| 58 | |||
| 59 | |||
| 60 | /** |
||
| 61 | * standard SS variable |
||
| 62 | * @var String |
||
| 63 | */ |
||
| 64 | private static $plural_name = "Tax Charges"; |
||
| 65 | public function i18n_plural_name() |
||
| 69 | |||
| 70 | |||
| 71 | // ######################################## *** cms variables + functions (e.g. getCMSFields, $searchableFields) |
||
| 72 | |||
| 73 | |||
| 74 | |||
| 75 | /** |
||
| 76 | * standard SS method |
||
| 77 | * @return FieldList for CMS |
||
| 78 | */ |
||
| 79 | public function getCMSFields() |
||
| 101 | |||
| 102 | // ######################################## *** other (non) static variables (e.g. private static $special_name_for_something, protected $order) |
||
| 103 | /** |
||
| 104 | * default country for tax calculations |
||
| 105 | * IMPORTANT: we need this variable - because in case of INCLUSIVE prices, |
||
| 106 | * we need to know on what country the prices are based as to be able |
||
| 107 | * to remove the tax for other countries. |
||
| 108 | * @var String |
||
| 109 | */ |
||
| 110 | private static $default_country_code = ""; |
||
| 111 | protected static function get_default_country_code_combined() |
||
| 119 | |||
| 120 | /** |
||
| 121 | * wording in cart for prices that are tax exclusive (tax added on top of prices) |
||
| 122 | * @var String |
||
| 123 | */ |
||
| 124 | private static $exclusive_explanation = ""; |
||
| 125 | |||
| 126 | /** |
||
| 127 | * wording in cart for prices that are tax inclusive (tax is part of the prices) |
||
| 128 | * @var String |
||
| 129 | */ |
||
| 130 | private static $inclusive_explanation = ""; |
||
| 131 | |||
| 132 | /** |
||
| 133 | * wording in cart for prices that are include a tax refund. |
||
| 134 | * A refund situation applies when the prices are tax inclusive |
||
| 135 | * but NO tax applies to the country to which the goods are sold. |
||
| 136 | * E.g. for a UK shop no VAT is charged to buyers outside the EU. |
||
| 137 | * @var String |
||
| 138 | */ |
||
| 139 | private static $refund_title = "Tax Exemption"; |
||
| 140 | |||
| 141 | /** |
||
| 142 | * wording in cart for prices that are tax exempt (no tax applies) |
||
| 143 | * @var String |
||
| 144 | */ |
||
| 145 | private static $no_tax_description = "Tax-exempt"; |
||
| 146 | |||
| 147 | /** |
||
| 148 | * name of the method in the buyable OrderItem that works out the |
||
| 149 | * portion without tax. You can use this method by creating your own |
||
| 150 | * OrderItem class and adding a method there. This is by far the most |
||
| 151 | * flexible way to work out the tax on products with complex tax rules. |
||
| 152 | * @var String |
||
| 153 | */ |
||
| 154 | private static $order_item_function_for_tax_exclusive_portion = "portionWithoutTax";//PortionWithoutTax |
||
| 155 | |||
| 156 | /** |
||
| 157 | * Use this variable IF: |
||
| 158 | * |
||
| 159 | * a. you have localised prices for countries |
||
| 160 | * other than the default country |
||
| 161 | * |
||
| 162 | * b. prices on the website are TAX INCLUSIVE |
||
| 163 | * |
||
| 164 | * If not, the tax for an international for a |
||
| 165 | * site with tax inclusive prices will firstly |
||
| 166 | * deduct the default tax and then add the tax |
||
| 167 | * of the country at hand. |
||
| 168 | * |
||
| 169 | * @var Boolean |
||
| 170 | */ |
||
| 171 | private static $alternative_country_prices_already_include_their_own_tax = false;//PortionWithoutTax |
||
| 172 | |||
| 173 | /** |
||
| 174 | * contains all the applicable DEFAULT tax objects |
||
| 175 | * @var Object |
||
| 176 | */ |
||
| 177 | private static $default_tax_objects = null; |
||
| 178 | |||
| 179 | |||
| 180 | /** |
||
| 181 | * tells us the default tax objects tax rate |
||
| 182 | * @var Float | Null |
||
| 183 | */ |
||
| 184 | private static $default_tax_objects_rate = null; |
||
| 185 | |||
| 186 | |||
| 187 | /** |
||
| 188 | * contains all the applicable tax objects for the current order |
||
| 189 | * @var Object |
||
| 190 | */ |
||
| 191 | private static $current_tax_objects = null; |
||
| 192 | |||
| 193 | /** |
||
| 194 | * tells us the current tax objects tax rate |
||
| 195 | * @var NULL | Float |
||
| 196 | */ |
||
| 197 | private static $current_tax_objects_rate = null; |
||
| 198 | |||
| 199 | /** |
||
| 200 | * any calculation messages are added to the Debug Message |
||
| 201 | * @var String |
||
| 202 | */ |
||
| 203 | protected $debugMessage = ''; |
||
| 204 | |||
| 205 | |||
| 206 | // ######################################## *** CRUD functions (e.g. canEdit) |
||
| 207 | // ######################################## *** init and update functions |
||
| 208 | /** |
||
| 209 | * updates database fields |
||
| 210 | * @param Bool $force - run it, even if it has run already |
||
| 211 | * @return void |
||
| 212 | */ |
||
| 213 | public function runUpdate($force = true) |
||
| 225 | |||
| 226 | |||
| 227 | // ######################################## *** form functions (e. g. Showform and getform) |
||
| 228 | // ######################################## *** template functions (e.g. ShowInTable, TableTitle, etc...) ... USES DB VALUES |
||
| 229 | |||
| 230 | /** |
||
| 231 | * Can the user remove this modifier? |
||
| 232 | * standard OrderModifier Method |
||
| 233 | * @return Bool |
||
| 234 | */ |
||
| 235 | public function CanBeRemoved() |
||
| 239 | |||
| 240 | /** |
||
| 241 | * Show the GSTTaxModifier in the Cart? |
||
| 242 | * standard OrderModifier Method |
||
| 243 | * @return Bool |
||
| 244 | */ |
||
| 245 | public function ShowInTable() |
||
| 249 | |||
| 250 | |||
| 251 | // ######################################## *** inner calculations.... USES CALCULATED VALUES |
||
| 252 | |||
| 253 | /** |
||
| 254 | * works out what taxes apply in the default setup. |
||
| 255 | * we need this, because prices may include tax |
||
| 256 | * based on the default tax rate. |
||
| 257 | * |
||
| 258 | * @return ArrayList | FALSE of applicable taxes in the default country. |
||
| 259 | */ |
||
| 260 | protected function defaultTaxObjects() |
||
| 288 | |||
| 289 | |||
| 290 | /** |
||
| 291 | * returns an ArrayList of all applicable tax options |
||
| 292 | * @return ArrayList | Null |
||
| 293 | */ |
||
| 294 | protected function currentTaxObjects() |
||
| 321 | |||
| 322 | /** |
||
| 323 | * returns the sum of rates for the given taxObjects |
||
| 324 | * @param Object - ArrayList of tax options |
||
| 325 | * @return Float |
||
| 326 | */ |
||
| 327 | protected function workOutSumRate($taxObjects) |
||
| 341 | |||
| 342 | /** |
||
| 343 | * tells us if the tax for the current order is exclusive |
||
| 344 | * default: false |
||
| 345 | * @return Bool |
||
| 346 | */ |
||
| 347 | protected function isExclusive() |
||
| 351 | |||
| 352 | /** |
||
| 353 | * tells us if the tax for the current order is inclusive |
||
| 354 | * default: true |
||
| 355 | * @return Bool |
||
| 356 | */ |
||
| 357 | protected function isInclusive() |
||
| 383 | |||
| 384 | /** |
||
| 385 | * turns a standard rate into a calculation rate. |
||
| 386 | * That is, 0.125 for exclusive is 1/9 for inclusive rates |
||
| 387 | * default: true |
||
| 388 | * @param float $rate - input rate (e.g. 0.125 equals a 12.5% tax rate) |
||
| 389 | * @return float |
||
| 390 | */ |
||
| 391 | protected function turnRateIntoCalculationRate($rate) |
||
| 395 | |||
| 396 | /** |
||
| 397 | * works out the tax to pay for the order items, |
||
| 398 | * based on a rate and a country |
||
| 399 | * @param float $rate |
||
| 400 | * @param string $country |
||
| 401 | * @return float - amount of tax to pay |
||
| 402 | */ |
||
| 403 | protected function workoutOrderItemsTax($rate, $country) |
||
| 458 | |||
| 459 | /** |
||
| 460 | * this method is a bit of a hack. |
||
| 461 | * if a product variation does not have any specific tax rules |
||
| 462 | * but the product does, then it uses the rules from the product. |
||
| 463 | * @param DataObject $buyable |
||
| 464 | */ |
||
| 465 | public function dealWithProductVariationException($buyable) |
||
| 477 | |||
| 478 | /** |
||
| 479 | * works out the tax to pay for the order modifiers, |
||
| 480 | * based on a rate |
||
| 481 | * @param float $rate |
||
| 482 | * @return float - amount of tax to pay |
||
| 483 | */ |
||
| 484 | protected function workoutModifiersTax($rate, $country) |
||
| 550 | |||
| 551 | /** |
||
| 552 | * Are there Any taxes that do not apply to all products |
||
| 553 | * @return Boolean |
||
| 554 | */ |
||
| 555 | protected function hasExceptionTaxes() |
||
| 562 | |||
| 563 | // ######################################## *** calculate database fields: protected function Live[field name] ... USES CALCULATED VALUES |
||
| 564 | |||
| 565 | |||
| 566 | /** |
||
| 567 | * Used to save DefaultCountry to database |
||
| 568 | * |
||
| 569 | * determines value for DB field: Country |
||
| 570 | * @return String |
||
| 571 | */ |
||
| 572 | protected function LiveDefaultCountry() |
||
| 576 | |||
| 577 | /** |
||
| 578 | * Used to save Country to database |
||
| 579 | * |
||
| 580 | * determines value for DB field: Country |
||
| 581 | * @return String |
||
| 582 | */ |
||
| 583 | protected function LiveCountry() |
||
| 587 | |||
| 588 | /** |
||
| 589 | * determines value for the default rate |
||
| 590 | * @return Float |
||
| 591 | */ |
||
| 592 | protected function LiveDefaultRate() |
||
| 597 | |||
| 598 | /** |
||
| 599 | * Used to save CurrentRate to database |
||
| 600 | * |
||
| 601 | * determines value for DB field: Country |
||
| 602 | * @return Float |
||
| 603 | */ |
||
| 604 | protected function LiveCurrentRate() |
||
| 609 | |||
| 610 | /** |
||
| 611 | * Used to save TaxType to database |
||
| 612 | * |
||
| 613 | * determines value for DB field: TaxType |
||
| 614 | * @return String (Exclusive|Inclusive) |
||
| 615 | */ |
||
| 616 | protected function LiveTaxType() |
||
| 623 | |||
| 624 | /** |
||
| 625 | * temporary store of data for additional speed. |
||
| 626 | * @var Array |
||
| 627 | */ |
||
| 628 | |||
| 629 | private static $temp_raw_table_value = array(); |
||
| 630 | |||
| 631 | /** |
||
| 632 | * Used to save RawTableValue to database |
||
| 633 | * |
||
| 634 | * In case of a an exclusive rate, show what is actually added. |
||
| 635 | * In case of inclusive rate, show what is actually included. |
||
| 636 | * @return float |
||
| 637 | */ |
||
| 638 | protected function LiveRawTableValue() |
||
| 649 | |||
| 650 | /** |
||
| 651 | * Used to save DebugString to database |
||
| 652 | * @return float |
||
| 653 | */ |
||
| 654 | protected function LiveDebugString() |
||
| 658 | |||
| 659 | /** |
||
| 660 | * Used to save TableValue to database |
||
| 661 | * |
||
| 662 | * @return float |
||
| 663 | */ |
||
| 664 | protected function LiveTableValue() |
||
| 668 | |||
| 669 | /** |
||
| 670 | * Used to save Name to database |
||
| 671 | * @return String |
||
| 672 | */ |
||
| 673 | protected function LiveName() |
||
| 710 | |||
| 711 | /** |
||
| 712 | * Used to save CalculatedTotal to database |
||
| 713 | |||
| 714 | * works out the actual amount that needs to be deducted / added. |
||
| 715 | * The exclusive case is easy: just add the applicable tax |
||
| 716 | * |
||
| 717 | * The inclusive case: work out what was included and then work out what is applicable |
||
| 718 | * (current), then work out the difference. |
||
| 719 | * |
||
| 720 | * @return Float |
||
| 721 | */ |
||
| 722 | protected function LiveCalculatedTotal() |
||
| 767 | |||
| 768 | private static $field_or_method_to_use_for_sub_title = ""; |
||
| 769 | |||
| 770 | public function getTableSubTitle() |
||
| 783 | |||
| 784 | // ######################################## *** Type Functions (IsChargeable, IsDeductable, IsNoChange, IsRemoved) |
||
| 785 | |||
| 786 | // ######################################## *** standard database related functions (e.g. onBeforeWrite, onAfterWrite, etc...) |
||
| 787 | |||
| 788 | // ######################################## *** AJAX related functions |
||
| 789 | |||
| 790 | // ######################################## *** debug functions |
||
| 791 | } |
||
| 792 |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.