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 ProductVariation 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 ProductVariation, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
6 | class ProductVariation extends DataObject implements BuyableModel, EditableEcommerceObject |
||
|
|||
7 | { |
||
8 | /** |
||
9 | * Standard SS variable. |
||
10 | */ |
||
11 | private static $api_access = array( |
||
12 | 'view' => array( |
||
13 | 'Title', |
||
14 | 'Description', |
||
15 | 'FullName', |
||
16 | 'AllowPurchase', |
||
17 | 'InternalItemID', |
||
18 | 'NumberSold', |
||
19 | 'Price', |
||
20 | 'Weight', |
||
21 | 'Model', |
||
22 | 'Quantifier', |
||
23 | 'Version', |
||
24 | ), |
||
25 | ); |
||
26 | |||
27 | /** |
||
28 | * Standard SS variable. |
||
29 | */ |
||
30 | private static $db = array( |
||
31 | 'InternalItemID' => 'Varchar(30)', |
||
32 | 'Price' => 'Currency', |
||
33 | 'Weight' => 'Float', |
||
34 | 'Model' => 'Varchar(30)', |
||
35 | 'Quantifier' => 'Varchar(30)', |
||
36 | 'AllowPurchase' => 'Boolean', |
||
37 | 'Sort' => 'Int', |
||
38 | 'NumberSold' => 'Int', |
||
39 | 'Description' => 'Varchar(255)', |
||
40 | 'FullName' => 'Varchar(255)', |
||
41 | 'FullSiteTreeSort' => 'Varchar(110)', |
||
42 | ); |
||
43 | |||
44 | /** |
||
45 | * Standard SS variable. |
||
46 | */ |
||
47 | private static $has_one = array( |
||
48 | 'Product' => 'Product', |
||
49 | 'Image' => 'Product_Image', |
||
50 | ); |
||
51 | |||
52 | /** |
||
53 | * Standard SS variable. |
||
54 | */ |
||
55 | private static $many_many = array( |
||
56 | 'AttributeValues' => 'ProductAttributeValue', |
||
57 | ); |
||
58 | |||
59 | /** |
||
60 | * Standard SS variable. |
||
61 | */ |
||
62 | private static $casting = array( |
||
63 | 'Parent' => 'Product', |
||
64 | 'Title' => 'HTMLText', |
||
65 | 'Link' => 'Text', |
||
66 | 'AllowPurchaseNice' => 'Varchar', |
||
67 | 'CalculatedPrice' => 'Currency', |
||
68 | 'CalculatedPriceAsMoney' => 'Money', |
||
69 | ); |
||
70 | |||
71 | /** |
||
72 | * Standard SS variable. |
||
73 | */ |
||
74 | private static $defaults = array( |
||
75 | 'AllowPurchase' => 1, |
||
76 | ); |
||
77 | |||
78 | /** |
||
79 | * Standard SS variable. |
||
80 | */ |
||
81 | private static $versioning = array( |
||
82 | 'Stage', |
||
83 | ); |
||
84 | |||
85 | /** |
||
86 | * Standard SS variable. |
||
87 | */ |
||
88 | private static $extensions = array( |
||
89 | "Versioned('Stage')", |
||
90 | ); |
||
91 | |||
92 | /** |
||
93 | * Standard SS variable. |
||
94 | */ |
||
95 | private static $indexes = array( |
||
96 | 'Sort' => true, |
||
97 | 'FullName' => true, |
||
98 | 'FullSiteTreeSort' => true, |
||
99 | ); |
||
100 | |||
101 | /** |
||
102 | * Standard SS variable. |
||
103 | */ |
||
104 | private static $field_labels = array( |
||
105 | 'Description' => 'Title (optional)', |
||
106 | ); |
||
107 | |||
108 | /** |
||
109 | * Standard SS variable. |
||
110 | */ |
||
111 | private static $summary_fields = array( |
||
112 | 'CMSThumbnail' => 'Image', |
||
113 | 'Title' => 'Title', |
||
114 | 'Price' => 'Price', |
||
115 | 'AllowPurchaseNice' => 'For Sale', |
||
116 | ); |
||
117 | |||
118 | /** |
||
119 | * Standard SS variable. |
||
120 | */ |
||
121 | private static $searchable_fields = array( |
||
122 | 'FullName' => array( |
||
123 | 'title' => 'Keyword', |
||
124 | 'field' => 'TextField', |
||
125 | 'filter' => 'PartialMatchFilter', |
||
126 | ), |
||
127 | 'Price' => array( |
||
128 | 'title' => 'Price', |
||
129 | 'field' => 'NumericField', |
||
130 | ), |
||
131 | 'InternalItemID' => array( |
||
132 | 'title' => 'Internal Item ID', |
||
133 | 'filter' => 'PartialMatchFilter', |
||
134 | ), |
||
135 | 'AllowPurchase', |
||
136 | ); |
||
137 | |||
138 | /** |
||
139 | * Standard SS variable. |
||
140 | */ |
||
141 | private static $default_sort = '"AllowPurchase" DESC, "FullSiteTreeSort" ASC, "Sort" ASC, "InternalItemID" ASC, "Price" ASC'; |
||
142 | |||
143 | /** |
||
144 | * Standard SS variable. |
||
145 | */ |
||
146 | private static $singular_name = 'Product Variation'; |
||
147 | public function i18n_singular_name() |
||
151 | |||
152 | /** |
||
153 | * Standard SS variable. |
||
154 | */ |
||
155 | private static $plural_name = 'Product Variations'; |
||
156 | public function i18n_plural_name() |
||
166 | |||
167 | /** |
||
168 | * How is the title build up? |
||
169 | * |
||
170 | * @var array |
||
171 | **/ |
||
172 | private static $title_style_option = array( |
||
173 | 'default' => array( |
||
174 | 'ShowType' => true, |
||
175 | 'BetweenTypeAndValue' => ': ', |
||
176 | 'BetweenVariations' => ', ', |
||
177 | ), |
||
178 | ); |
||
179 | |||
180 | /** |
||
181 | * change the way the title of the variation is displayed |
||
182 | * @param string $code key |
||
183 | * @param string $showType do we show the type (e.g. colour, size)? |
||
184 | * @param string $betweenTypeAndValue e.g. a semi-colon (:) |
||
185 | * @param string $betweenVariations e.g. a comma (,) |
||
186 | */ |
||
187 | public static function add_title_style_option($code, $showType, $betweenTypeAndValue, $betweenVariations) |
||
196 | |||
197 | /** |
||
198 | * remove style option by key |
||
199 | * @param string $code key |
||
200 | */ |
||
201 | public static function remove_title_style_option($code) |
||
205 | |||
206 | private static $current_style_option_code = 'default'; |
||
207 | |||
208 | public static function get_current_style_option_array() |
||
212 | |||
213 | /** |
||
214 | * Standard SS method. |
||
215 | * |
||
216 | * @return FieldSet |
||
217 | */ |
||
218 | public function getCMSFields() |
||
338 | |||
339 | /** |
||
340 | * link to edit the record. |
||
341 | * |
||
342 | * @param string | Null $action - e.g. edit |
||
343 | * |
||
344 | * @return string |
||
345 | */ |
||
346 | public function CMSEditLink($action = null) |
||
354 | |||
355 | /** |
||
356 | * Use the sort order of the variation attributes to order the attribute values. |
||
357 | * This ensures that when VariationAttributes is used for a table header |
||
358 | * and AttributeValues are used for the table rows then the columns will be |
||
359 | * in the same order. |
||
360 | * |
||
361 | * @return DataObjectSet |
||
362 | */ |
||
363 | public function AttributeValuesSorted() |
||
374 | |||
375 | /** |
||
376 | * standard SS method. |
||
377 | */ |
||
378 | public function populateDefaults() |
||
383 | |||
384 | /** |
||
385 | * Puts together a title for the Product Variation. |
||
386 | * |
||
387 | * @return string |
||
388 | */ |
||
389 | public function Title() |
||
415 | |||
416 | /** |
||
417 | * shorthand. |
||
418 | * |
||
419 | * @return string |
||
420 | */ |
||
421 | public function FullDescription() |
||
425 | |||
426 | /** |
||
427 | * shorthand. |
||
428 | * |
||
429 | * @return string |
||
430 | */ |
||
431 | public function ImgAltTag() |
||
435 | |||
436 | /** |
||
437 | * returns YES or NO for the CMS Fields. |
||
438 | * |
||
439 | * @return string |
||
440 | */ |
||
441 | public function AllowPurchaseNice() |
||
445 | |||
446 | protected $currentStageOfRequest = ''; |
||
447 | |||
448 | /** |
||
449 | * when we save this object, should we save the parent |
||
450 | * as well? |
||
451 | * |
||
452 | * @var bool |
||
453 | */ |
||
454 | protected $saveParentProduct = false; |
||
455 | |||
456 | /** |
||
457 | * By setting this to TRUE |
||
458 | * the parent (product) will be save when this object will be saved. |
||
459 | * |
||
460 | * @param bool $b |
||
461 | */ |
||
462 | public function setSaveParentProduct($b) |
||
466 | |||
467 | /** |
||
468 | * standard SS method |
||
469 | * sets the FullName + FullSiteTreeSort of the variation. |
||
470 | */ |
||
471 | public function onBeforeWrite() |
||
478 | |||
479 | /** |
||
480 | * sets the FullName and FullSiteTreeField to the latest values |
||
481 | * This can be useful as you can compare it to the ones saved in the database. |
||
482 | * Returns true if the value is different from the one in the database. |
||
483 | * |
||
484 | * @return bool |
||
485 | */ |
||
486 | public function prepareFullFields() |
||
512 | |||
513 | /** |
||
514 | * Standard SS Method. |
||
515 | */ |
||
516 | public function onAfterWrite() |
||
536 | |||
537 | /** |
||
538 | * Standard SS Method |
||
539 | * Remove links to Attribute Values. |
||
540 | */ |
||
541 | public function onBeforeDelete() |
||
546 | |||
547 | /** |
||
548 | * this is used by TableListField to access attribute values. |
||
549 | * |
||
550 | * @return DataObject |
||
551 | */ |
||
552 | public function AttributeProxy() |
||
563 | |||
564 | //GROUPS AND SIBLINGS |
||
565 | |||
566 | /** |
||
567 | * We use this function to make it more universal. |
||
568 | * For a buyable, a parent could refer to a ProductGroup OR a Product. |
||
569 | * |
||
570 | * @return DataObject | Null |
||
571 | **/ |
||
572 | public function Parent() |
||
580 | |||
581 | /** |
||
582 | * Returns the direct parent (group) for the product. |
||
583 | **/ |
||
584 | public function MainParentGroup() |
||
588 | |||
589 | /** |
||
590 | * Returns Buybales in the same group. |
||
591 | **/ |
||
592 | public function Siblings() |
||
596 | |||
597 | //IMAGES |
||
598 | /** |
||
599 | * returns a "BestAvailable" image if the current one is not available |
||
600 | * In some cases this is appropriate and in some cases this is not. |
||
601 | * For example, consider the following setup |
||
602 | * - product A with three variations |
||
603 | * - Product A has an image, but the variations have no images |
||
604 | * With this scenario, you want to show ONLY the product image |
||
605 | * on the product page, but if one of the variations is added to the |
||
606 | * cart, then you want to show the product image. |
||
607 | * This can be achieved bu using the BestAvailable image. |
||
608 | * |
||
609 | * @return Image | Null |
||
610 | */ |
||
611 | public function BestAvailableImage() |
||
621 | |||
622 | /** |
||
623 | * Little hack to show thumbnail in summary fields in modeladmin in CMS. |
||
624 | * |
||
625 | * @return string (HTML = formatted image) |
||
626 | */ |
||
627 | public function CMSThumbnail() |
||
637 | |||
638 | /** |
||
639 | * Returns a link to a default image. |
||
640 | * If a default image is set in the site config then this link is returned |
||
641 | * Otherwise, a standard link is returned. |
||
642 | * |
||
643 | * @return string |
||
644 | */ |
||
645 | public function DefaultImageLink() |
||
649 | |||
650 | /** |
||
651 | * returns the default image of the product. |
||
652 | * |
||
653 | * @return Image | Null |
||
654 | */ |
||
655 | public function DefaultImage() |
||
659 | |||
660 | /** |
||
661 | * returns a product image for use in templates |
||
662 | * e.g. $DummyImage.Width();. |
||
663 | * |
||
664 | * @return Product_Image |
||
665 | */ |
||
666 | public function DummyImage() |
||
670 | |||
671 | // VERSIONING |
||
672 | |||
673 | /** |
||
674 | * Action to return specific version of a product. |
||
675 | * This is really useful for sold products where you want to retrieve the actual version that you sold. |
||
676 | * |
||
677 | * @TODO: this is not correct yet, as the versions of product and productvariation are muddled up! |
||
678 | * |
||
679 | * @param HTTPRequest $request |
||
680 | */ |
||
681 | public function viewversion($request) |
||
701 | |||
702 | /** |
||
703 | * Action to return specific version of a product variation. |
||
704 | * This can be any product to enable the retrieval of deleted products. |
||
705 | * This is really useful for sold products where you want to retrieve the actual version that you sold. |
||
706 | * |
||
707 | * @param int $id |
||
708 | * @param int $version |
||
709 | * |
||
710 | * @return DataObject | Null |
||
711 | */ |
||
712 | public function getVersionOfBuyable($id = 0, $version = 0) |
||
723 | |||
724 | //ORDER ITEM |
||
725 | |||
726 | /** |
||
727 | * returns the order item associated with the buyable. |
||
728 | * ALWAYS returns one, even if there is none in the cart. |
||
729 | * Does not write to database. |
||
730 | * |
||
731 | * @return OrderItem (no kidding) |
||
732 | **/ |
||
733 | public function OrderItem() |
||
753 | |||
754 | /** |
||
755 | * @var string |
||
756 | */ |
||
757 | protected $defaultClassNameForOrderItem = 'ProductVariation_OrderItem'; |
||
758 | |||
759 | /** |
||
760 | * you can overwrite this function in your buyable items (such as Product). |
||
761 | * |
||
762 | * @return string |
||
763 | **/ |
||
764 | public function classNameForOrderItem() |
||
774 | |||
775 | /** |
||
776 | * You can set an alternative class name for order item using this method. |
||
777 | * |
||
778 | * @param string $ClassName |
||
779 | **/ |
||
780 | public function setAlternativeClassNameForOrderItem($className) |
||
784 | |||
785 | /** |
||
786 | * When purchasing this buyable, how many decimals can it have? |
||
787 | * |
||
788 | * @return int |
||
789 | */ |
||
790 | public function QuantityDecimals() |
||
794 | |||
795 | /** |
||
796 | * Number of variations sold. |
||
797 | * |
||
798 | * @TODO: check if we need to use other class names |
||
799 | * |
||
800 | * |
||
801 | * @return int |
||
802 | */ |
||
803 | public function HasBeenSold() |
||
821 | |||
822 | //LINKS |
||
823 | |||
824 | /** |
||
825 | * Takes you to the Product and filters |
||
826 | * for the provided variation. |
||
827 | * |
||
828 | * @param string $action - OPTIONAL |
||
829 | * |
||
830 | * @return string |
||
831 | */ |
||
832 | public function Link($action = null) |
||
840 | |||
841 | |||
842 | /** |
||
843 | * |
||
844 | * @todo TEST!!!! |
||
845 | * @return string |
||
846 | */ |
||
847 | public function VersionedLink() |
||
858 | |||
859 | /** |
||
860 | * passing on shopping cart links ...is this necessary?? ...why not just pass the cart? |
||
861 | * |
||
862 | * @return string |
||
863 | */ |
||
864 | public function AddLink() |
||
868 | |||
869 | /** |
||
870 | * link use to add (one) to cart. |
||
871 | * |
||
872 | *@return string |
||
873 | */ |
||
874 | public function IncrementLink() |
||
879 | |||
880 | /** |
||
881 | * Link used to remove one from cart |
||
882 | * we can do this, because by default remove link removes one. |
||
883 | * |
||
884 | * @return string |
||
885 | */ |
||
886 | public function DecrementLink() |
||
890 | |||
891 | /** |
||
892 | * remove one buyable's orderitem from cart. |
||
893 | * |
||
894 | * @return string (Link) |
||
895 | */ |
||
896 | public function RemoveLink() |
||
900 | |||
901 | /** |
||
902 | * remove all of this buyable's orderitem from cart. |
||
903 | * |
||
904 | * @return string (Link) |
||
905 | */ |
||
906 | public function RemoveAllLink() |
||
910 | |||
911 | /** |
||
912 | * remove all of this buyable's orderitem from cart and go through to this buyble to add alternative selection. |
||
913 | * |
||
914 | * @return string (Link) |
||
915 | */ |
||
916 | public function RemoveAllAndEditLink() |
||
920 | |||
921 | /** |
||
922 | * set new specific new quantity for buyable's orderitem. |
||
923 | * |
||
924 | * @param float |
||
925 | * |
||
926 | * @return string (Link) |
||
927 | */ |
||
928 | public function SetSpecificQuantityItemLink($quantity) |
||
932 | |||
933 | /** |
||
934 | * @return string |
||
935 | */ |
||
936 | public function AddToCartAndGoToCheckoutLink() |
||
943 | |||
944 | /** |
||
945 | * Here you can add additional information to your product |
||
946 | * links such as the AddLink and the RemoveLink. |
||
947 | * One useful parameter you can add is the BackURL link. |
||
948 | * |
||
949 | * Usage would be by means of |
||
950 | * 1. decorating product |
||
951 | * 2. adding a updateLinkParameters method |
||
952 | * 3. adding items to the array. |
||
953 | * |
||
954 | * You can also extend Product and override this method... |
||
955 | * |
||
956 | * @return array |
||
957 | **/ |
||
958 | protected function linkParameters() |
||
970 | |||
971 | //TEMPLATE STUFF |
||
972 | |||
973 | /** |
||
974 | * @return bool |
||
975 | */ |
||
976 | public function IsInCart() |
||
980 | |||
981 | /** |
||
982 | * @return EcomQuantityField |
||
983 | */ |
||
984 | public function EcomQuantityField() |
||
990 | |||
991 | /** |
||
992 | * returns the instance of EcommerceConfigAjax for use in templates. |
||
993 | * In templates, it is used like this: |
||
994 | * $EcommerceConfigAjax.TableID. |
||
995 | * |
||
996 | * @return EcommerceConfigAjax |
||
997 | **/ |
||
998 | public function AJAXDefinitions() |
||
1002 | |||
1003 | /** |
||
1004 | * @return EcommerceDBConfig |
||
1005 | **/ |
||
1006 | public function EcomConfig() |
||
1010 | |||
1011 | /** |
||
1012 | * Is it a variation? |
||
1013 | * |
||
1014 | * @return bool |
||
1015 | */ |
||
1016 | public function IsProductVariation() |
||
1020 | |||
1021 | /** |
||
1022 | * returns the actual price worked out after discounts, currency conversions, etc... |
||
1023 | * |
||
1024 | * @casted |
||
1025 | * |
||
1026 | * @return float |
||
1027 | */ |
||
1028 | public function CalculatedPrice() |
||
1042 | |||
1043 | /** |
||
1044 | * How do we display the price? |
||
1045 | * |
||
1046 | * @return Money |
||
1047 | */ |
||
1048 | public function CalculatedPriceAsMoney() |
||
1056 | |||
1057 | //CRUD SETTINGS |
||
1058 | |||
1059 | /** |
||
1060 | * Is the product for sale? |
||
1061 | * |
||
1062 | * @return bool |
||
1063 | */ |
||
1064 | public function canPurchase(Member $member = null, $checkPrice = true) |
||
1091 | |||
1092 | /** |
||
1093 | * standard SS Method |
||
1094 | * we explicitely set this to give access in the API. |
||
1095 | * |
||
1096 | * @return bool |
||
1097 | */ |
||
1098 | public function canView($member = null) |
||
1106 | |||
1107 | /** |
||
1108 | * Shop Admins can edit. |
||
1109 | * |
||
1110 | * @return bool |
||
1111 | */ |
||
1112 | public function canEdit($member = null) |
||
1123 | |||
1124 | /** |
||
1125 | * Standard SS method. |
||
1126 | * |
||
1127 | * @return bool |
||
1128 | */ |
||
1129 | View Code Duplication | public function canDelete($member = null) |
|
1138 | |||
1139 | /** |
||
1140 | * Standard SS method |
||
1141 | * //check if it is in a current cart? |
||
1142 | * |
||
1143 | * @return bool |
||
1144 | */ |
||
1145 | View Code Duplication | public function canDeleteFromLive($member = null) |
|
1154 | |||
1155 | /** |
||
1156 | * Standard SS method. |
||
1157 | * |
||
1158 | * @return bool |
||
1159 | */ |
||
1160 | View Code Duplication | public function canCreate($member = null) |
|
1169 | |||
1170 | /** |
||
1171 | * finds similar ("siblings") variations where one |
||
1172 | * attribute value is NOT the same. |
||
1173 | * |
||
1174 | * @return DataList |
||
1175 | */ |
||
1176 | public function MostLikeMe() |
||
1207 | } |
||
1208 |
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.