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_CLI_Coupon 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_CLI_Coupon, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class WC_CLI_Coupon extends WC_CLI_Command { |
||
12 | |||
13 | /** |
||
14 | * Create a coupon. |
||
15 | * |
||
16 | * ## OPTIONS |
||
17 | * |
||
18 | * [--<field>=<value>] |
||
19 | * : Associative args for the new coupon. |
||
20 | * |
||
21 | * [--porcelain] |
||
22 | * : Outputs just the new coupon id. |
||
23 | * |
||
24 | * ## AVAILABLE FIELDS |
||
25 | * |
||
26 | * These fields are available for create command: |
||
27 | * |
||
28 | * * code |
||
29 | * * type |
||
30 | * * amount |
||
31 | * * description |
||
32 | * * expiry_date |
||
33 | * * individual_use |
||
34 | * * product_ids |
||
35 | * * exclude_product_ids |
||
36 | * * usage_limit |
||
37 | * * usage_limit_per_user |
||
38 | * * limit_usage_to_x_items |
||
39 | * * usage_count |
||
40 | * * enable_free_shipping |
||
41 | * * product_category_ids |
||
42 | * * exclude_product_category_ids |
||
43 | * * minimum_amount |
||
44 | * * maximum_amount |
||
45 | * * customer_emails |
||
46 | * |
||
47 | * ## EXAMPLES |
||
48 | * |
||
49 | * wp wc coupon create --code=new-coupon --type=percent |
||
50 | * |
||
51 | */ |
||
52 | public function create( $__, $assoc_args ) { |
||
154 | |||
155 | /** |
||
156 | * Delete one or more coupons. |
||
157 | * |
||
158 | * ## OPTIONS |
||
159 | * |
||
160 | * <id>... |
||
161 | * : The coupon ID to delete. |
||
162 | * |
||
163 | * ## EXAMPLES |
||
164 | * |
||
165 | * wp wc coupon delete 123 |
||
166 | * |
||
167 | * wp wc coupon delete $(wp wc coupon list --format=ids) |
||
168 | * |
||
169 | */ |
||
170 | public function delete( $args, $assoc_args ) { |
||
185 | |||
186 | /** |
||
187 | * Get a coupon. |
||
188 | * |
||
189 | * ## OPTIONS |
||
190 | * |
||
191 | * <coupon> |
||
192 | * : Coupon ID or code |
||
193 | * |
||
194 | * [--field=<field>] |
||
195 | * : Instead of returning the whole coupon fields, returns the value of a single fields. |
||
196 | * |
||
197 | * [--fields=<fields>] |
||
198 | * : Get a specific subset of the coupon's fields. |
||
199 | * |
||
200 | * [--format=<format>] |
||
201 | * : Accepted values: table, json, csv. Default: table. |
||
202 | * |
||
203 | * ## AVAILABLE FIELDS |
||
204 | * |
||
205 | * These fields are available for get command: |
||
206 | * |
||
207 | * * id |
||
208 | * * code |
||
209 | * * type |
||
210 | * * amount |
||
211 | * * description |
||
212 | * * expiry_date |
||
213 | * * individual_use |
||
214 | * * product_ids |
||
215 | * * exclude_product_ids |
||
216 | * * usage_limit |
||
217 | * * usage_limit_per_user |
||
218 | * * limit_usage_to_x_items |
||
219 | * * usage_count |
||
220 | * * enable_free_shipping |
||
221 | * * product_category_ids |
||
222 | * * exclude_product_category_ids |
||
223 | * * minimum_amount |
||
224 | * * maximum_amount |
||
225 | * * customer_emails |
||
226 | * |
||
227 | * ## EXAMPLES |
||
228 | * |
||
229 | * wp wc coupon get 123 --field=discount_type |
||
230 | * |
||
231 | * wp wc coupon get disc50 --format=json > disc50.json |
||
232 | * |
||
233 | * @since 2.5.0 |
||
234 | */ |
||
235 | public function get( $args, $assoc_args ) { |
||
282 | |||
283 | /** |
||
284 | * List coupons. |
||
285 | * |
||
286 | * ## OPTIONS |
||
287 | * |
||
288 | * [--<field>=<value>] |
||
289 | * : Filter coupon based on coupon property. |
||
290 | * |
||
291 | * [--field=<field>] |
||
292 | * : Prints the value of a single field for each coupon. |
||
293 | * |
||
294 | * [--fields=<fields>] |
||
295 | * : Limit the output to specific coupon fields. |
||
296 | * |
||
297 | * [--format=<format>] |
||
298 | * : Acceptec values: table, csv, json, count, ids. Default: table. |
||
299 | * |
||
300 | * ## AVAILABLE FIELDS |
||
301 | * |
||
302 | * These fields will be displayed by default for each coupon: |
||
303 | * |
||
304 | * * id |
||
305 | * * code |
||
306 | * * type |
||
307 | * * amount |
||
308 | * * description |
||
309 | * * expiry_date |
||
310 | * |
||
311 | * These fields are optionally available: |
||
312 | * |
||
313 | * * individual_use |
||
314 | * * product_ids |
||
315 | * * exclude_product_ids |
||
316 | * * usage_limit |
||
317 | * * usage_limit_per_user |
||
318 | * * limit_usage_to_x_items |
||
319 | * * usage_count |
||
320 | * * free_shipping |
||
321 | * * product_category_ids |
||
322 | * * exclude_product_category_ids |
||
323 | * * exclude_sale_items |
||
324 | * * minimum_amount |
||
325 | * * maximum_amount |
||
326 | * * customer_emails |
||
327 | * |
||
328 | * Fields for filtering query result also available: |
||
329 | * |
||
330 | * * q Filter coupons with search query. |
||
331 | * * in Specify coupon IDs to retrieve. |
||
332 | * * not_in Specify coupon IDs NOT to retrieve. |
||
333 | * * created_at_min Filter coupons created after this date. |
||
334 | * * created_at_max Filter coupons created before this date. |
||
335 | * * updated_at_min Filter coupons updated after this date. |
||
336 | * * updated_at_max Filter coupons updated before this date. |
||
337 | * * page Page number. |
||
338 | * * offset Number of coupon to displace or pass over. |
||
339 | * * order Accepted values: ASC and DESC. Default: DESC. |
||
340 | * * orderby Sort retrieved coupons by parameter. One or more options can be passed. |
||
341 | * |
||
342 | * ## EXAMPLES |
||
343 | * |
||
344 | * wp wc coupon list |
||
345 | * |
||
346 | * wp wc coupon list --field=id |
||
347 | * |
||
348 | * wp wc coupon list --fields=id,code,type --format=json |
||
349 | * |
||
350 | * @since 2.5.0 |
||
351 | * @subcommand list |
||
352 | */ |
||
353 | View Code Duplication | public function list_( $__, $assoc_args ) { |
|
367 | |||
368 | /** |
||
369 | * Get coupon types. |
||
370 | * |
||
371 | * ## EXAMPLES |
||
372 | * |
||
373 | * wp wc coupon types |
||
374 | * |
||
375 | * @since 2.5.0 |
||
376 | */ |
||
377 | public function types( $__, $___ ) { |
||
383 | |||
384 | /** |
||
385 | * Update one or more coupons. |
||
386 | * |
||
387 | * ## OPTIONS |
||
388 | * |
||
389 | * <coupon> |
||
390 | * : The ID or code of the coupon to update. |
||
391 | * |
||
392 | * [--<field>=<value>] |
||
393 | * : One or more fields to update. |
||
394 | * |
||
395 | * ## AVAILABLE FIELDS |
||
396 | * |
||
397 | * These fields are available for update command: |
||
398 | * |
||
399 | * * code |
||
400 | * * type |
||
401 | * * amount |
||
402 | * * description |
||
403 | * * expiry_date |
||
404 | * * individual_use |
||
405 | * * product_ids |
||
406 | * * exclude_product_ids |
||
407 | * * usage_limit |
||
408 | * * usage_limit_per_user |
||
409 | * * limit_usage_to_x_items |
||
410 | * * usage_count |
||
411 | * * enable_free_shipping |
||
412 | * * product_category_ids |
||
413 | * * exclude_product_categories |
||
414 | * * exclude_product_category_ids |
||
415 | * * minimum_amount |
||
416 | * * maximum_amount |
||
417 | * * customer_emails |
||
418 | * |
||
419 | * ## EXAMPLES |
||
420 | * |
||
421 | * wp wc coupon update 123 --amount=5 |
||
422 | * |
||
423 | * wp wc coupon update coupon-code --code=new-coupon-code |
||
424 | * |
||
425 | * @since 2.5.0 |
||
426 | */ |
||
427 | public function update( $args, $assoc_args ) { |
||
428 | try { |
||
429 | $coupon = $this->get_coupon_from_id_or_code( $args[0] ); |
||
430 | View Code Duplication | if ( ! $coupon ) { |
|
431 | throw new WC_CLI_Exception( 'woocommerce_cli_invalid_coupon', sprintf( __( 'Invalid coupon ID or code: %s', 'woocommerce' ), $args[0] ) ); |
||
432 | } |
||
433 | |||
434 | $id = $coupon->id; |
||
435 | $coupon_code = $coupon->code; |
||
436 | $data = apply_filters( 'woocommerce_cli_update_coupon_data', $assoc_args, $id ); |
||
437 | if ( isset( $data['code'] ) ) { |
||
438 | global $wpdb; |
||
439 | |||
440 | $coupon_code = apply_filters( 'woocommerce_coupon_code', $data['code'] ); |
||
441 | |||
442 | // Check for duplicate coupon codes |
||
443 | $coupon_found = $wpdb->get_var( $wpdb->prepare( " |
||
444 | SELECT $wpdb->posts.ID |
||
445 | FROM $wpdb->posts |
||
446 | WHERE $wpdb->posts.post_type = 'shop_coupon' |
||
447 | AND $wpdb->posts.post_status = 'publish' |
||
448 | AND $wpdb->posts.post_title = '%s' |
||
449 | AND $wpdb->posts.ID != %s |
||
450 | ", $coupon_code, $id ) ); |
||
451 | |||
452 | if ( $coupon_found ) { |
||
453 | throw new WC_CLI_Exception( 'woocommerce_cli_coupon_code_already_exists', __( 'The coupon code already exists', 'woocommerce' ) ); |
||
454 | } |
||
455 | } |
||
456 | |||
457 | $id = wp_update_post( array( 'ID' => intval( $id ), 'post_title' => $coupon_code, 'post_excerpt' => isset( $data['description'] ) ? $data['description'] : '' ) ); |
||
458 | if ( 0 === $id ) { |
||
459 | throw new WC_CLI_Exception( 'woocommerce_cli_cannot_update_coupon', __( 'Failed to update coupon', 'woocommerce' ) ); |
||
460 | } |
||
461 | |||
462 | if ( isset( $data['type'] ) ) { |
||
463 | // Validate coupon types. |
||
464 | if ( ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_coupon_types() ) ) ) { |
||
465 | throw new WC_CLI_Exception( 'woocommerce_cli_invalid_coupon_type', sprintf( __( 'Invalid coupon type - the coupon type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_coupon_types() ) ) ) ); |
||
466 | } |
||
467 | update_post_meta( $id, 'discount_type', $data['type'] ); |
||
468 | } |
||
469 | |||
470 | if ( isset( $data['amount'] ) ) { |
||
471 | update_post_meta( $id, 'coupon_amount', wc_format_decimal( $data['amount'] ) ); |
||
472 | } |
||
473 | |||
474 | if ( isset( $data['individual_use'] ) ) { |
||
475 | update_post_meta( $id, 'individual_use', ( $this->is_true( $data['individual_use'] ) ) ? 'yes' : 'no' ); |
||
476 | } |
||
477 | |||
478 | View Code Duplication | if ( isset( $data['product_ids'] ) ) { |
|
479 | update_post_meta( $id, 'product_ids', implode( ',', array_filter( array_map( 'intval', $data['product_ids'] ) ) ) ); |
||
480 | } |
||
481 | |||
482 | View Code Duplication | if ( isset( $data['exclude_product_ids'] ) ) { |
|
483 | update_post_meta( $id, 'exclude_product_ids', implode( ',', array_filter( array_map( 'intval', $data['exclude_product_ids'] ) ) ) ); |
||
484 | } |
||
485 | |||
486 | if ( isset( $data['usage_limit'] ) ) { |
||
487 | update_post_meta( $id, 'usage_limit', absint( $data['usage_limit'] ) ); |
||
488 | } |
||
489 | |||
490 | if ( isset( $data['usage_limit_per_user'] ) ) { |
||
491 | update_post_meta( $id, 'usage_limit_per_user', absint( $data['usage_limit_per_user'] ) ); |
||
492 | } |
||
493 | |||
494 | if ( isset( $data['limit_usage_to_x_items'] ) ) { |
||
495 | update_post_meta( $id, 'limit_usage_to_x_items', absint( $data['limit_usage_to_x_items'] ) ); |
||
496 | } |
||
497 | |||
498 | if ( isset( $data['usage_count'] ) ) { |
||
499 | update_post_meta( $id, 'usage_count', absint( $data['usage_count'] ) ); |
||
500 | } |
||
501 | |||
502 | if ( isset( $data['expiry_date'] ) ) { |
||
503 | update_post_meta( $id, 'expiry_date', $this->get_coupon_expiry_date( wc_clean( $data['expiry_date'] ) ) ); |
||
504 | } |
||
505 | |||
506 | if ( isset( $data['enable_free_shipping'] ) ) { |
||
507 | update_post_meta( $id, 'free_shipping', ( $this->is_true( $data['enable_free_shipping'] ) ) ? 'yes' : 'no' ); |
||
508 | } |
||
509 | |||
510 | View Code Duplication | if ( isset( $data['product_category_ids'] ) ) { |
|
511 | update_post_meta( $id, 'product_categories', array_filter( array_map( 'intval', $data['product_category_ids'] ) ) ); |
||
512 | } |
||
513 | |||
514 | View Code Duplication | if ( isset( $data['exclude_product_category_ids'] ) ) { |
|
515 | update_post_meta( $id, 'exclude_product_categories', array_filter( array_map( 'intval', $data['exclude_product_category_ids'] ) ) ); |
||
516 | } |
||
517 | |||
518 | if ( isset( $data['exclude_sale_items'] ) ) { |
||
519 | update_post_meta( $id, 'exclude_sale_items', ( $this->is_true( $data['exclude_sale_items'] ) ) ? 'yes' : 'no' ); |
||
520 | } |
||
521 | |||
522 | if ( isset( $data['minimum_amount'] ) ) { |
||
523 | update_post_meta( $id, 'minimum_amount', wc_format_decimal( $data['minimum_amount'] ) ); |
||
524 | } |
||
525 | |||
526 | if ( isset( $data['maximum_amount'] ) ) { |
||
527 | update_post_meta( $id, 'maximum_amount', wc_format_decimal( $data['maximum_amount'] ) ); |
||
528 | } |
||
529 | |||
530 | View Code Duplication | if ( isset( $data['customer_emails'] ) ) { |
|
531 | update_post_meta( $id, 'customer_email', array_filter( array_map( 'sanitize_email', $data['customer_emails'] ) ) ); |
||
532 | } |
||
533 | |||
534 | do_action( 'woocommerce_cli_update_coupon', $id, $data ); |
||
535 | |||
536 | WP_CLI::success( "Updated coupon $id." ); |
||
537 | } catch ( WC_CLI_Exception $e ) { |
||
538 | WP_CLI::error( $e->getMessage() ); |
||
539 | } |
||
540 | } |
||
541 | |||
542 | /** |
||
543 | * Get query args for list subcommand. |
||
544 | * |
||
545 | * @since 2.5.0 |
||
546 | * @return array |
||
547 | */ |
||
548 | protected function get_list_query_args() { |
||
556 | |||
557 | /** |
||
558 | * Get default format fields that will be used in `list` and `get` subcommands. |
||
559 | * |
||
560 | * @since 2.5.0 |
||
561 | * @return string |
||
562 | */ |
||
563 | protected function get_default_format_fields() { |
||
566 | |||
567 | /** |
||
568 | * Format posts from WP_Query result to items in which each item contain |
||
569 | * common properties of item, for instance `post_title` will be `code`. |
||
570 | * |
||
571 | * @since 2.5.0 |
||
572 | * @param array $posts Array of post |
||
573 | * @return array Items |
||
574 | */ |
||
575 | protected function format_posts_to_items( $posts ) { |
||
606 | |||
607 | /** |
||
608 | * Get expiry_date format before saved into DB. |
||
609 | * |
||
610 | * @since 2.5.0 |
||
611 | * @param string $expiry_date |
||
612 | * @return string |
||
613 | */ |
||
614 | protected function get_coupon_expiry_date( $expiry_date ) { |
||
621 | |||
622 | /** |
||
623 | * Get coupon from coupon's ID or code. |
||
624 | * |
||
625 | * @since 2.5.0 |
||
626 | * @param int|string $coupon_id_or_code Coupon's ID or code |
||
627 | * @param bool $display_warning Display warning if ID or code is invalid. Default false. |
||
628 | * @return WC_Coupon |
||
629 | */ |
||
630 | protected function get_coupon_from_id_or_code( $coupon_id_or_code, $display_warning = false ) { |
||
643 | |||
644 | /** |
||
645 | * Get coupon from coupon's ID or code. |
||
646 | * |
||
647 | * @since 2.5.0 |
||
648 | * @param array $args Coupon's IDs or codes |
||
649 | * @param bool $display_warning Display warning if ID or code is invalid. Default false. |
||
650 | * @return WC_Coupon |
||
651 | */ |
||
652 | protected function get_many_coupons_from_ids_or_codes( $args, $display_warning = false ) { |
||
664 | } |
||
665 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.