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_Webhook 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_Webhook, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 21 | class WC_Webhook { |
||
| 22 | |||
| 23 | /** @var int webhook ID (post ID) */ |
||
| 24 | public $id; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * Setup webhook & load post data. |
||
| 28 | * |
||
| 29 | * @since 2.2 |
||
| 30 | * @param string|int $id |
||
| 31 | * @return \WC_Webhook |
||
|
|
|||
| 32 | */ |
||
| 33 | public function __construct( $id ) { |
||
| 44 | |||
| 45 | |||
| 46 | /** |
||
| 47 | * Magic isset as a wrapper around metadata_exists(). |
||
| 48 | * |
||
| 49 | * @since 2.2 |
||
| 50 | * @param string $key |
||
| 51 | * @return bool true if $key isset, false otherwise |
||
| 52 | */ |
||
| 53 | public function __isset( $key ) { |
||
| 59 | |||
| 60 | |||
| 61 | /** |
||
| 62 | * Magic get, wraps get_post_meta() for all keys except $status. |
||
| 63 | * |
||
| 64 | * @since 2.2 |
||
| 65 | * @param string $key |
||
| 66 | * @return mixed value |
||
| 67 | */ |
||
| 68 | public function __get( $key ) { |
||
| 78 | |||
| 79 | |||
| 80 | /** |
||
| 81 | * Enqueue the hooks associated with the webhook. |
||
| 82 | * |
||
| 83 | * @since 2.2 |
||
| 84 | */ |
||
| 85 | public function enqueue() { |
||
| 95 | |||
| 96 | |||
| 97 | /** |
||
| 98 | * Process the webhook for delivery by verifying that it should be delivered. |
||
| 99 | * and scheduling the delivery (in the background by default, or immediately). |
||
| 100 | * |
||
| 101 | * @since 2.2 |
||
| 102 | * @param mixed $arg the first argument provided from the associated hooks |
||
| 103 | */ |
||
| 104 | public function process( $arg ) { |
||
| 125 | |||
| 126 | /** |
||
| 127 | * Helper to check if the webhook should be delivered, as some hooks. |
||
| 128 | * (like `wp_trash_post`) will fire for every post type, not just ours. |
||
| 129 | * |
||
| 130 | * @since 2.2 |
||
| 131 | * @param mixed $arg first hook argument |
||
| 132 | * @return bool true if webhook should be delivered, false otherwise |
||
| 133 | */ |
||
| 134 | private function should_deliver( $arg ) { |
||
| 135 | $should_deliver = true; |
||
| 136 | $current_action = current_action(); |
||
| 137 | |||
| 138 | // only active webhooks can be delivered |
||
| 139 | if ( 'active' != $this->get_status() ) { |
||
| 140 | $should_deliver = false; |
||
| 141 | |||
| 142 | // only deliver deleted event for coupons, orders, and products |
||
| 143 | } elseif ( 'delete_post' === $current_action && ! in_array( $GLOBALS['post_type'], array( 'shop_coupon', 'shop_order', 'product' ) ) ) { |
||
| 144 | $should_deliver = false; |
||
| 145 | |||
| 146 | } elseif ( 'delete_user' == $current_action ) { |
||
| 147 | $user = get_userdata( absint( $arg ) ); |
||
| 148 | |||
| 149 | // only deliver deleted customer event for users with customer role |
||
| 150 | if ( ! $user || ! in_array( 'customer', (array) $user->roles ) ) { |
||
| 151 | $should_deliver = false; |
||
| 152 | } |
||
| 153 | |||
| 154 | // check if the custom order type has chosen to exclude order webhooks from triggering along with its own webhooks. |
||
| 155 | } elseif ( 'order' == $this->get_resource() && ! in_array( get_post_type( absint( $arg ) ), wc_get_order_types( 'order-webhooks' ) ) ) { |
||
| 156 | $should_deliver = false; |
||
| 157 | |||
| 158 | } elseif ( 0 === strpos( $current_action, 'woocommerce_process_shop' ) || 0 === strpos( $current_action, 'woocommerce_process_product' ) ) { |
||
| 159 | // the `woocommerce_process_shop_*` and `woocommerce_process_product_*` hooks |
||
| 160 | // fire for create and update of products and orders, so check the post |
||
| 161 | // creation date to determine the actual event |
||
| 162 | $resource = get_post( absint( $arg ) ); |
||
| 163 | |||
| 164 | // a resource is considered created when the hook is executed within 10 seconds of the post creation date |
||
| 165 | $resource_created = ( ( time() - 10 ) <= strtotime( $resource->post_date_gmt ) ); |
||
| 166 | |||
| 167 | if ( 'created' == $this->get_event() && ! $resource_created ) { |
||
| 168 | $should_deliver = false; |
||
| 169 | } elseif ( 'updated' == $this->get_event() && $resource_created ) { |
||
| 170 | $should_deliver = false; |
||
| 171 | } |
||
| 172 | } |
||
| 173 | |||
| 174 | /* |
||
| 175 | * Let other plugins intercept deliver for some messages queue like rabbit/zeromq |
||
| 176 | */ |
||
| 177 | return apply_filters( 'woocommerce_webhook_should_deliver', $should_deliver, $this, $arg ); |
||
| 178 | } |
||
| 179 | |||
| 180 | |||
| 181 | /** |
||
| 182 | * Deliver the webhook payload using wp_safe_remote_request(). |
||
| 183 | * |
||
| 184 | * @since 2.2 |
||
| 185 | * @param mixed $arg First hook argument. |
||
| 186 | */ |
||
| 187 | public function deliver( $arg ) { |
||
| 226 | |||
| 227 | |||
| 228 | /** |
||
| 229 | * Build the payload data for the webhook. |
||
| 230 | * |
||
| 231 | * @since 2.2 |
||
| 232 | * @param mixed $resource_id first hook argument, typically the resource ID |
||
| 233 | * @return mixed payload data |
||
| 234 | */ |
||
| 235 | private function build_payload( $resource_id ) { |
||
| 295 | |||
| 296 | |||
| 297 | /** |
||
| 298 | * Generate a base64-encoded HMAC-SHA256 signature of the payload body so the. |
||
| 299 | * recipient can verify the authenticity of the webhook. Note that the signature. |
||
| 300 | * is calculated after the body has already been encoded (JSON by default). |
||
| 301 | * |
||
| 302 | * @since 2.2 |
||
| 303 | * @param string $payload payload data to hash |
||
| 304 | * @return string hash |
||
| 305 | */ |
||
| 306 | public function generate_signature( $payload ) { |
||
| 312 | |||
| 313 | |||
| 314 | /** |
||
| 315 | * Create a new comment for log the delivery request/response and. |
||
| 316 | * return the ID for inclusion in the webhook request. |
||
| 317 | * |
||
| 318 | * @since 2.2 |
||
| 319 | * @return int delivery (comment) ID |
||
| 320 | */ |
||
| 321 | public function get_new_delivery_id() { |
||
| 337 | |||
| 338 | |||
| 339 | /** |
||
| 340 | * Log the delivery request/response. |
||
| 341 | * |
||
| 342 | * @since 2.2 |
||
| 343 | * @param int $delivery_id previously created comment ID |
||
| 344 | * @param array $request request data |
||
| 345 | * @param array|WP_Error $response response data |
||
| 346 | * @param float $duration request duration |
||
| 347 | */ |
||
| 348 | public function log_delivery( $delivery_id, $request, $response, $duration ) { |
||
| 404 | |||
| 405 | /** |
||
| 406 | * Track consecutive delivery failures and automatically disable the webhook. |
||
| 407 | * if more than 5 consecutive failures occur. A failure is defined as a. |
||
| 408 | * non-2xx response. |
||
| 409 | * |
||
| 410 | * @since 2.2 |
||
| 411 | */ |
||
| 412 | private function failed_delivery() { |
||
| 425 | |||
| 426 | |||
| 427 | /** |
||
| 428 | * Get the delivery logs for this webhook. |
||
| 429 | * |
||
| 430 | * @since 2.2 |
||
| 431 | * @return array |
||
| 432 | */ |
||
| 433 | public function get_delivery_logs() { |
||
| 458 | |||
| 459 | |||
| 460 | /** |
||
| 461 | * Get the delivery log specified by the ID. The delivery log includes: |
||
| 462 | * |
||
| 463 | * + duration |
||
| 464 | * + summary |
||
| 465 | * + request method/url |
||
| 466 | * + request headers/body |
||
| 467 | * + response code/message/headers/body |
||
| 468 | * |
||
| 469 | * @since 2.2 |
||
| 470 | * @param int $delivery_id |
||
| 471 | * @return bool|array false if invalid delivery ID, array of log data otherwise |
||
| 472 | */ |
||
| 473 | public function get_delivery_log( $delivery_id ) { |
||
| 499 | |||
| 500 | /** |
||
| 501 | * Set the webhook topic and associated hooks. The topic resource & event. |
||
| 502 | * are also saved separately. |
||
| 503 | * |
||
| 504 | * @since 2.2 |
||
| 505 | * @param string $topic |
||
| 506 | */ |
||
| 507 | public function set_topic( $topic ) { |
||
| 528 | |||
| 529 | /** |
||
| 530 | * Get the associated hook names for a topic. |
||
| 531 | * |
||
| 532 | * @since 2.2 |
||
| 533 | * @param string $topic |
||
| 534 | * @return array hook names |
||
| 535 | */ |
||
| 536 | private function get_topic_hooks( $topic ) { |
||
| 594 | |||
| 595 | /** |
||
| 596 | * Send a test ping to the delivery URL, sent when the webhook is first created. |
||
| 597 | * |
||
| 598 | * @since 2.2 |
||
| 599 | * @return bool|WP_Error |
||
| 600 | */ |
||
| 601 | public function deliver_ping() { |
||
| 602 | $args = array( |
||
| 603 | 'user-agent' => sprintf( 'WooCommerce/%s Hookshot (WordPress/%s)', WC_VERSION, $GLOBALS['wp_version'] ), |
||
| 604 | 'body' => "webhook_id={$this->id}", |
||
| 605 | ); |
||
| 606 | |||
| 607 | $test = wp_safe_remote_post( $this->get_delivery_url(), $args ); |
||
| 608 | $response_code = wp_remote_retrieve_response_code( $test ); |
||
| 609 | |||
| 610 | View Code Duplication | if ( is_wp_error( $test ) ) { |
|
| 611 | return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL cannot be reached: %s', 'woocommerce' ), $test->get_error_message() ) ); |
||
| 612 | } |
||
| 613 | |||
| 614 | if ( 200 !== $response_code ) { |
||
| 615 | return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL returned response code: %s', 'woocommerce' ), absint( $response_code ) ) ); |
||
| 616 | } |
||
| 617 | |||
| 618 | return true; |
||
| 619 | } |
||
| 620 | |||
| 621 | /** |
||
| 622 | * Get the webhook status: |
||
| 623 | * |
||
| 624 | * + `active` - delivers payload. |
||
| 625 | * + `paused` - does not deliver payload, paused by admin. |
||
| 626 | * + `disabled` - does not delivery payload, paused automatically due to. |
||
| 627 | * consecutive failures. |
||
| 628 | * |
||
| 629 | * @since 2.2 |
||
| 630 | * @return string status |
||
| 631 | */ |
||
| 632 | public function get_status() { |
||
| 654 | |||
| 655 | /** |
||
| 656 | * Get the webhook i18n status. |
||
| 657 | * |
||
| 658 | * @return string |
||
| 659 | */ |
||
| 660 | public function get_i18n_status() { |
||
| 666 | |||
| 667 | /** |
||
| 668 | * Update the webhook status, see get_status() for valid statuses. |
||
| 669 | * |
||
| 670 | * @since 2.2 |
||
| 671 | * @param $status |
||
| 672 | */ |
||
| 673 | public function update_status( $status ) { |
||
| 698 | |||
| 699 | /** |
||
| 700 | * Set the delivery URL. |
||
| 701 | * |
||
| 702 | * @since 2.2 |
||
| 703 | * @param string $url |
||
| 704 | */ |
||
| 705 | public function set_delivery_url( $url ) { |
||
| 710 | |||
| 711 | /** |
||
| 712 | * Get the delivery URL. |
||
| 713 | * |
||
| 714 | * @since 2.2 |
||
| 715 | * @return string |
||
| 716 | */ |
||
| 717 | public function get_delivery_url() { |
||
| 721 | |||
| 722 | /** |
||
| 723 | * Set the secret used for generating the HMAC-SHA256 signature. |
||
| 724 | * |
||
| 725 | * @since 2.2 |
||
| 726 | * @param string $secret |
||
| 727 | */ |
||
| 728 | public function set_secret( $secret ) { |
||
| 732 | |||
| 733 | /** |
||
| 734 | * Get the secret used for generating the HMAC-SHA256 signature. |
||
| 735 | * |
||
| 736 | * @since 2.2 |
||
| 737 | * @return string |
||
| 738 | */ |
||
| 739 | public function get_secret() { |
||
| 742 | |||
| 743 | /** |
||
| 744 | * Get the friendly name for the webhook. |
||
| 745 | * |
||
| 746 | * @since 2.2 |
||
| 747 | * @return string |
||
| 748 | */ |
||
| 749 | public function get_name() { |
||
| 752 | |||
| 753 | /** |
||
| 754 | * Get the webhook topic, e.g. `order.created`. |
||
| 755 | * |
||
| 756 | * @since 2.2 |
||
| 757 | * @return string |
||
| 758 | */ |
||
| 759 | public function get_topic() { |
||
| 762 | |||
| 763 | /** |
||
| 764 | * Get the hook names for the webhook. |
||
| 765 | * |
||
| 766 | * @since 2.2 |
||
| 767 | * @return array hook names |
||
| 768 | */ |
||
| 769 | public function get_hooks() { |
||
| 772 | |||
| 773 | /** |
||
| 774 | * Get the resource for the webhook, e.g. `order`. |
||
| 775 | * |
||
| 776 | * @since 2.2 |
||
| 777 | * @return string |
||
| 778 | */ |
||
| 779 | public function get_resource() { |
||
| 782 | |||
| 783 | /** |
||
| 784 | * Get the event for the webhook, e.g. `created`. |
||
| 785 | * |
||
| 786 | * @since 2.2 |
||
| 787 | * @return string |
||
| 788 | */ |
||
| 789 | public function get_event() { |
||
| 792 | |||
| 793 | /** |
||
| 794 | * Get the failure count. |
||
| 795 | * |
||
| 796 | * @since 2.2 |
||
| 797 | * @return int |
||
| 798 | */ |
||
| 799 | public function get_failure_count() { |
||
| 802 | |||
| 803 | /** |
||
| 804 | * Get the user ID for this webhook. |
||
| 805 | * |
||
| 806 | * @since 2.2 |
||
| 807 | * @return int|string user ID |
||
| 808 | */ |
||
| 809 | public function get_user_id() { |
||
| 812 | |||
| 813 | /** |
||
| 814 | * Get the post data for the webhook. |
||
| 815 | * |
||
| 816 | * @since 2.2 |
||
| 817 | * @return null|WP_Post |
||
| 818 | */ |
||
| 819 | public function get_post_data() { |
||
| 822 | |||
| 823 | } |
||
| 824 |
Adding a
@returnannotation to a constructor is not recommended, since a constructor does not have a meaningful return value.Please refer to the PHP core documentation on constructors.