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 WC_Shipping 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 WC_Shipping, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 21 | class WC_Shipping { |
||
| 22 | |||
| 23 | /** @var bool True if shipping is enabled. */ |
||
| 24 | public $enabled = false; |
||
| 25 | |||
| 26 | /** @var array|null Stores methods loaded into woocommerce. */ |
||
| 27 | public $shipping_methods = null; |
||
| 28 | |||
| 29 | /** @var float Stores the cost of shipping */ |
||
| 30 | public $shipping_total = 0; |
||
| 31 | |||
| 32 | /** @var array Stores an array of shipping taxes. */ |
||
| 33 | public $shipping_taxes = array(); |
||
| 34 | |||
| 35 | /** @var array Stores the shipping classes. */ |
||
| 36 | public $shipping_classes = array(); |
||
| 37 | |||
| 38 | /** @var array Stores packages to ship and to get quotes for. */ |
||
| 39 | public $packages = array(); |
||
| 40 | |||
| 41 | /** |
||
| 42 | * @var WC_Shipping The single instance of the class |
||
| 43 | * @since 2.1 |
||
| 44 | */ |
||
| 45 | protected static $_instance = null; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * Main WC_Shipping Instance. |
||
| 49 | * |
||
| 50 | * Ensures only one instance of WC_Shipping is loaded or can be loaded. |
||
| 51 | * |
||
| 52 | * @since 2.1 |
||
| 53 | * @static |
||
| 54 | * @return WC_Shipping Main instance |
||
| 55 | */ |
||
| 56 | public static function instance() { |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Cloning is forbidden. |
||
| 65 | * |
||
| 66 | * @since 2.1 |
||
| 67 | */ |
||
| 68 | public function __clone() { |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Unserializing instances of this class is forbidden. |
||
| 74 | * |
||
| 75 | * @since 2.1 |
||
| 76 | */ |
||
| 77 | public function __wakeup() { |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Initialize shipping. |
||
| 83 | */ |
||
| 84 | public function __construct() { |
||
| 91 | |||
| 92 | /** |
||
| 93 | * Initialize shipping. |
||
| 94 | */ |
||
| 95 | public function init() { |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Shipping methods register themselves by returning their main class name through the woocommerce_shipping_methods filter. |
||
| 101 | * @return array |
||
| 102 | */ |
||
| 103 | public function get_shipping_method_class_names() { |
||
| 123 | |||
| 124 | /** |
||
| 125 | * Loads all shipping methods which are hooked in. If a $package is passed some methods may add themselves conditionally. |
||
| 126 | * |
||
| 127 | * Loads all shipping methods which are hooked in. |
||
| 128 | * If a $package is passed some methods may add themselves conditionally and zones will be used. |
||
| 129 | * |
||
| 130 | * @param array $package |
||
| 131 | * @return array |
||
| 132 | */ |
||
| 133 | public function load_shipping_methods( $package = array() ) { |
||
| 152 | |||
| 153 | /** |
||
| 154 | * Register a shipping method. |
||
| 155 | * |
||
| 156 | * @param object|string $method Either the name of the method's class, or an instance of the method's class. |
||
| 157 | */ |
||
| 158 | public function register_shipping_method( $method ) { |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Unregister shipping methods. |
||
| 170 | */ |
||
| 171 | public function unregister_shipping_methods() { |
||
| 174 | |||
| 175 | /** |
||
| 176 | * Returns all registered shipping methods for usage. |
||
| 177 | * |
||
| 178 | * @access public |
||
| 179 | * @return array |
||
| 180 | */ |
||
| 181 | public function get_shipping_methods() { |
||
| 187 | |||
| 188 | /** |
||
| 189 | * Get an array of shipping classes. |
||
| 190 | * |
||
| 191 | * @access public |
||
| 192 | * @return array |
||
| 193 | */ |
||
| 194 | public function get_shipping_classes() { |
||
| 201 | |||
| 202 | /** |
||
| 203 | * Get the default method. |
||
| 204 | * @param array $available_methods |
||
| 205 | * @param boolean $current_chosen_method |
||
| 206 | * @return string |
||
| 207 | */ |
||
| 208 | private function get_default_method( $available_methods, $current_chosen_method = false ) { |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Calculate shipping for (multiple) packages of cart items. |
||
| 228 | * |
||
| 229 | * @param array $packages multi-dimensional array of cart items to calc shipping for |
||
| 230 | */ |
||
| 231 | public function calculate_shipping( $packages = array() ) { |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Calculate shipping rates for a package, |
||
| 312 | * |
||
| 313 | * Calculates each shipping methods cost. Rates are stored in the session based on the package hash to avoid re-calculation every page load. |
||
| 314 | * |
||
| 315 | * @param array $package cart items |
||
| 316 | * @param int $package_key Index of the package being calculated. Used to cache multiple package rates. |
||
| 317 | * @return array |
||
| 318 | */ |
||
| 319 | public function calculate_shipping_for_package( $package = array(), $package_key = 0 ) { |
||
| 320 | if ( ! $this->enabled || ! $package ) { |
||
| 321 | return false; |
||
| 322 | } |
||
| 323 | |||
| 324 | // Check if we need to recalculate shipping for this package |
||
| 325 | $package_hash = 'wc_ship_' . md5( json_encode( $package ) . WC_Cache_Helper::get_transient_version( 'shipping' ) ); |
||
| 326 | $status_options = get_option( 'woocommerce_status_options', array() ); |
||
| 327 | $session_key = 'shipping_for_package_' . $package_key; |
||
| 328 | $stored_rates = WC()->session->get( $session_key ); |
||
| 329 | |||
| 330 | if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || ! empty( $status_options['shipping_debug_mode'] ) ) { |
||
| 331 | // Calculate shipping method rates |
||
| 332 | $package['rates'] = array(); |
||
| 333 | |||
| 334 | foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) { |
||
| 335 | // Shipping instances need an ID |
||
| 336 | if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) { |
||
| 337 | $package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package ); // + instead of array_merge maintains numeric keys |
||
| 338 | } |
||
| 339 | } |
||
| 340 | |||
| 341 | // Filter the calculated rates |
||
| 342 | $package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package ); |
||
| 343 | |||
| 344 | // Store in session to avoid recalculation |
||
| 345 | WC()->session->set( $session_key, array( |
||
| 346 | 'package_hash' => $package_hash, |
||
| 347 | 'rates' => $package['rates'] |
||
| 348 | ) ); |
||
| 349 | } else { |
||
| 350 | $package['rates'] = $stored_rates['rates']; |
||
| 351 | } |
||
| 352 | |||
| 353 | return $package; |
||
| 354 | } |
||
| 355 | |||
| 356 | /** |
||
| 357 | * Get packages. |
||
| 358 | * @return array |
||
| 359 | */ |
||
| 360 | public function get_packages() { |
||
| 363 | |||
| 364 | /** |
||
| 365 | * Reset shipping. |
||
| 366 | * |
||
| 367 | * Reset the totals for shipping as a whole. |
||
| 368 | */ |
||
| 369 | public function reset_shipping() { |
||
| 375 | |||
| 376 | /** |
||
| 377 | * @deprecated 2.6.0 Was previously used to determine sort order of methods, but this is now controlled by zones and thus unused. |
||
| 378 | */ |
||
| 379 | public function sort_shipping_methods() { |
||
| 383 | } |
||
| 384 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)or! empty(...)instead.