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
@return
annotation 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.