Complex classes like ShoppingCart 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 ShoppingCart, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 12 | class ShoppingCart |
||
|
|
|||
| 13 | { |
||
| 14 | private static $cartid_session_name = 'shoppingcartid'; |
||
| 15 | |||
| 16 | private $order; |
||
| 17 | |||
| 18 | private $calculateonce = false; |
||
| 19 | |||
| 20 | private $message; |
||
| 21 | |||
| 22 | private $type; |
||
| 23 | |||
| 24 | private static $instance; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * Access for only allowing access to one (singleton) ShoppingCart. |
||
| 28 | * |
||
| 29 | * @return ShoppingCart |
||
| 30 | */ |
||
| 31 | 90 | public static function singleton() |
|
| 39 | |||
| 40 | /** |
||
| 41 | * Shortened alias for ShoppingCart::singleton()->current() |
||
| 42 | * |
||
| 43 | * @return Order |
||
| 44 | */ |
||
| 45 | 16 | public static function curr() |
|
| 49 | |||
| 50 | /** |
||
| 51 | * Singleton prevents constructing a ShoppingCart any other way. |
||
| 52 | */ |
||
| 53 | 2 | private function __construct() |
|
| 56 | |||
| 57 | /** |
||
| 58 | * Get the current order, or return null if it doesn't exist. |
||
| 59 | * |
||
| 60 | * @return Order |
||
| 61 | */ |
||
| 62 | 90 | public function current() |
|
| 80 | |||
| 81 | /** |
||
| 82 | * Set the current cart |
||
| 83 | * |
||
| 84 | * @param Order |
||
| 85 | * |
||
| 86 | * @return ShoppingCart |
||
| 87 | */ |
||
| 88 | 7 | public function setCurrent(Order $cart) |
|
| 98 | |||
| 99 | /** |
||
| 100 | * Helper that only allows orders to be started internally. |
||
| 101 | * |
||
| 102 | * @return Order |
||
| 103 | */ |
||
| 104 | 17 | protected function findOrMake() |
|
| 119 | |||
| 120 | /** |
||
| 121 | * Adds an item to the cart |
||
| 122 | * |
||
| 123 | * @param Buyable $buyable |
||
| 124 | * @param number $quantity |
||
| 125 | * @param unknown $filter |
||
| 126 | * |
||
| 127 | * @return boolean|OrderItem false or the new/existing item |
||
| 128 | */ |
||
| 129 | 16 | public function add(Buyable $buyable, $quantity = 1, $filter = array()) |
|
| 130 | { |
||
| 131 | 16 | $order = $this->findOrMake(); |
|
| 132 | 16 | $order->extend("beforeAdd", $buyable, $quantity, $filter); |
|
| 133 | 16 | if (!$buyable) { |
|
| 134 | |||
| 135 | return $this->error(_t("ShoppingCart.ProductNotFound", "Product not found.")); |
||
| 136 | } |
||
| 137 | 16 | $item = $this->findOrMakeItem($buyable, $filter); |
|
| 138 | 16 | if (!$item) { |
|
| 139 | |||
| 140 | 1 | return false; |
|
| 141 | } |
||
| 142 | 16 | if (!$item->_brandnew) { |
|
| 143 | 3 | $item->Quantity += $quantity; |
|
| 144 | 3 | } else { |
|
| 145 | 16 | $item->Quantity = $quantity; |
|
| 146 | } |
||
| 147 | 16 | $item->write(); |
|
| 148 | 16 | $order->extend("afterAdd", $item, $buyable, $quantity, $filter); |
|
| 149 | 16 | $this->message(_t("ShoppingCart.ItemAdded", "Item has been added successfully.")); |
|
| 150 | |||
| 151 | 16 | return $item; |
|
| 152 | } |
||
| 153 | |||
| 154 | /** |
||
| 155 | * Remove an item from the cart. |
||
| 156 | * |
||
| 157 | * @param id or Buyable $buyable |
||
| 158 | * @param $item |
||
| 159 | * @param int $quantity - number of items to remove, or leave null for all items (default) |
||
| 160 | * |
||
| 161 | * @return boolean success/failure |
||
| 162 | */ |
||
| 163 | 4 | public function remove(Buyable $buyable, $quantity = null, $filter = array()) |
|
| 164 | { |
||
| 165 | 4 | $order = $this->current(); |
|
| 166 | |||
| 167 | 4 | if (!$order) { |
|
| 168 | return $this->error(_t("ShoppingCart.NoOrder", "No current order.")); |
||
| 169 | } |
||
| 170 | |||
| 171 | 4 | $order->extend("beforeRemove", $buyable, $quantity, $filter); |
|
| 172 | |||
| 173 | 4 | $item = $this->get($buyable, $filter); |
|
| 174 | |||
| 175 | 4 | if (!$item) { |
|
| 176 | 1 | return false; |
|
| 177 | } |
||
| 178 | |||
| 179 | //if $quantity will become 0, then remove all |
||
| 180 | 4 | if (!$quantity || ($item->Quantity - $quantity) <= 0) { |
|
| 181 | 4 | $item->delete(); |
|
| 182 | 4 | $item->destroy(); |
|
| 183 | 4 | } else { |
|
| 184 | 1 | $item->Quantity -= $quantity; |
|
| 185 | 1 | $item->write(); |
|
| 186 | } |
||
| 187 | 4 | $order->extend("afterRemove", $item, $buyable, $quantity, $filter); |
|
| 188 | 4 | $this->message(_t("ShoppingCart.ItemRemoved", "Item has been successfully removed.")); |
|
| 189 | |||
| 190 | 4 | return true; |
|
| 191 | } |
||
| 192 | |||
| 193 | /** |
||
| 194 | * Sets the quantity of an item in the cart. |
||
| 195 | * Will automatically add or remove item, if necessary. |
||
| 196 | * |
||
| 197 | * @param id or Buyable $buyable |
||
| 198 | * @param $item |
||
| 199 | * @param int $quantity |
||
| 200 | * |
||
| 201 | * @return boolean|OrderItem false or the new/existing item |
||
| 202 | */ |
||
| 203 | 3 | public function setQuantity(Buyable $buyable, $quantity = 1, $filter = array()) |
|
| 222 | |||
| 223 | /** |
||
| 224 | * Finds or makes an order item for a given product + filter. |
||
| 225 | * |
||
| 226 | * @param id or Buyable $buyable |
||
| 227 | * @param string $filter |
||
| 228 | * |
||
| 229 | * @return OrderItem the found or created item |
||
| 230 | */ |
||
| 231 | 17 | private function findOrMakeItem(Buyable $buyable, $filter = array()) |
|
| 267 | |||
| 268 | /** |
||
| 269 | * Finds an existing order item. |
||
| 270 | * |
||
| 271 | * @param Buyable $buyable |
||
| 272 | * @param string $filter |
||
| 273 | * |
||
| 274 | * @return the item requested, or false |
||
| 275 | */ |
||
| 276 | 21 | public function get(Buyable $buyable, $customfilter = array()) |
|
| 277 | { |
||
| 278 | 20 | $order = $this->current(); |
|
| 279 | 20 | if (!$buyable || !$order) { |
|
| 280 | 3 | return false; |
|
| 281 | } |
||
| 282 | 1 | $filter = array( |
|
| 283 | 20 | 'OrderID' => $order->ID, |
|
| 284 | 19 | ); |
|
| 285 | 19 | $itemclass = Config::inst()->get(get_class($buyable), 'order_item'); |
|
| 286 | 19 | $relationship = Config::inst()->get($itemclass, 'buyable_relationship'); |
|
| 287 | 19 | $filter[$relationship . "ID"] = $buyable->ID; |
|
| 288 | 19 | $required = array('Order', $relationship); |
|
| 289 | 19 | if (is_array($itemclass::config()->required_fields)) { |
|
| 290 | 19 | $required = array_merge($required, $itemclass::config()->required_fields); |
|
| 291 | 19 | } |
|
| 292 | 19 | $query = new MatchObjectFilter($itemclass, array_merge($customfilter, $filter), $required); |
|
| 293 | 19 | $item = $itemclass::get()->where($query->getFilter())->first(); |
|
| 294 | 19 | if (!$item) { |
|
| 295 | 19 | return $this->error(_t("ShoppingCart.ItemNotFound", "Item not found.")); |
|
| 296 | } |
||
| 297 | |||
| 298 | 11 | return $item; |
|
| 299 | } |
||
| 300 | |||
| 301 | /** |
||
| 302 | * Store old cart id in session order history |
||
| 303 | * @param int|null $requestedOrderId optional parameter that denotes the order that was requested |
||
| 304 | */ |
||
| 305 | 1 | public function archiveorderid($requestedOrderId = null) |
|
| 322 | |||
| 323 | /** |
||
| 324 | * Empty / abandon the entire cart. |
||
| 325 | * |
||
| 326 | * @return bool - true if successful, false if no cart found |
||
| 327 | */ |
||
| 328 | 10 | public function clear() |
|
| 341 | |||
| 342 | /** |
||
| 343 | * Store a new error. |
||
| 344 | */ |
||
| 345 | 20 | protected function error($message) |
|
| 351 | |||
| 352 | /** |
||
| 353 | * Store a message to be fed back to user. |
||
| 354 | * |
||
| 355 | * @param string $message |
||
| 356 | * @param string $type - good, bad, warning |
||
| 357 | */ |
||
| 358 | 21 | protected function message($message, $type = "good") |
|
| 363 | |||
| 364 | public function getMessage() |
||
| 368 | |||
| 369 | public function getMessageType() |
||
| 373 | |||
| 374 | public function clearMessage() |
||
| 378 | |||
| 379 | //singleton protection |
||
| 380 | public function __clone() |
||
| 384 | |||
| 385 | public function __wakeup() |
||
| 389 | } |
||
| 390 | |||
| 672 |
The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.
Some resources for further reading:
You can also find more detailed suggestions for refactoring in the “Code” section of your repository.