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 WP_REST_Terms_Controller 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 WP_REST_Terms_Controller, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
6 | class WP_REST_Terms_Controller extends WP_REST_Controller { |
||
7 | |||
8 | protected $taxonomy; |
||
9 | |||
10 | /** |
||
11 | * @param string $taxonomy |
||
12 | */ |
||
13 | View Code Duplication | public function __construct( $taxonomy ) { |
|
14 | $this->taxonomy = $taxonomy; |
||
15 | $this->namespace = 'wp/v2'; |
||
16 | $tax_obj = get_taxonomy( $taxonomy ); |
||
17 | $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name; |
||
18 | } |
||
19 | |||
20 | /** |
||
21 | * Register the routes for the objects of the controller. |
||
22 | */ |
||
23 | public function register_routes() { |
||
24 | |||
25 | register_rest_route( $this->namespace, '/' . $this->rest_base, array( |
||
26 | array( |
||
27 | 'methods' => WP_REST_Server::READABLE, |
||
28 | 'callback' => array( $this, 'get_items' ), |
||
29 | 'permission_callback' => array( $this, 'get_items_permissions_check' ), |
||
30 | 'args' => $this->get_collection_params(), |
||
31 | ), |
||
32 | array( |
||
33 | 'methods' => WP_REST_Server::CREATABLE, |
||
34 | 'callback' => array( $this, 'create_item' ), |
||
35 | 'permission_callback' => array( $this, 'create_item_permissions_check' ), |
||
36 | 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), |
||
37 | ), |
||
38 | 'schema' => array( $this, 'get_public_item_schema' ), |
||
39 | )); |
||
40 | register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( |
||
41 | array( |
||
42 | 'methods' => WP_REST_Server::READABLE, |
||
43 | 'callback' => array( $this, 'get_item' ), |
||
44 | 'permission_callback' => array( $this, 'get_item_permissions_check' ), |
||
45 | 'args' => array( |
||
46 | 'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
||
47 | ), |
||
48 | ), |
||
49 | array( |
||
50 | 'methods' => WP_REST_Server::EDITABLE, |
||
51 | 'callback' => array( $this, 'update_item' ), |
||
52 | 'permission_callback' => array( $this, 'update_item_permissions_check' ), |
||
53 | 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), |
||
54 | ), |
||
55 | array( |
||
56 | 'methods' => WP_REST_Server::DELETABLE, |
||
57 | 'callback' => array( $this, 'delete_item' ), |
||
58 | 'permission_callback' => array( $this, 'delete_item_permissions_check' ), |
||
59 | 'args' => array( |
||
60 | 'force' => array( |
||
61 | 'default' => false, |
||
62 | 'description' => __( 'Required to be true, as resource does not support trashing.' ), |
||
63 | ), |
||
64 | ), |
||
65 | ), |
||
66 | 'schema' => array( $this, 'get_public_item_schema' ), |
||
67 | ) ); |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * Check if a given request has access to read the terms. |
||
72 | * |
||
73 | * @param WP_REST_Request $request Full details about the request. |
||
74 | * @return WP_Error|boolean |
||
75 | */ |
||
76 | View Code Duplication | public function get_items_permissions_check( $request ) { |
|
86 | |||
87 | /** |
||
88 | * Get terms associated with a taxonomy |
||
89 | * |
||
90 | * @param WP_REST_Request $request Full details about the request |
||
91 | * @return WP_REST_Response|WP_Error |
||
92 | */ |
||
93 | public function get_items( $request ) { |
||
94 | $prepared_args = array( |
||
95 | 'exclude' => $request['exclude'], |
||
96 | 'include' => $request['include'], |
||
97 | 'order' => $request['order'], |
||
98 | 'orderby' => $request['orderby'], |
||
99 | 'post' => $request['post'], |
||
100 | 'hide_empty' => $request['hide_empty'], |
||
101 | 'number' => $request['per_page'], |
||
102 | 'search' => $request['search'], |
||
103 | 'slug' => $request['slug'], |
||
104 | ); |
||
105 | |||
106 | View Code Duplication | if ( ! empty( $request['offset'] ) ) { |
|
107 | $prepared_args['offset'] = $request['offset']; |
||
108 | } else { |
||
109 | $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; |
||
110 | } |
||
111 | |||
112 | $taxonomy_obj = get_taxonomy( $this->taxonomy ); |
||
113 | |||
114 | if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) { |
||
115 | if ( 0 === $request['parent'] ) { |
||
116 | // Only query top-level terms. |
||
117 | $prepared_args['parent'] = 0; |
||
118 | } else { |
||
119 | if ( $request['parent'] ) { |
||
120 | $prepared_args['parent'] = $request['parent']; |
||
121 | } |
||
122 | } |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * Filter the query arguments, before passing them to `get_terms()`. |
||
127 | * |
||
128 | * Enables adding extra arguments or setting defaults for a terms |
||
129 | * collection request. |
||
130 | * |
||
131 | * @see https://developer.wordpress.org/reference/functions/get_terms/ |
||
132 | * |
||
133 | * @param array $prepared_args Array of arguments to be |
||
134 | * passed to get_terms. |
||
135 | * @param WP_REST_Request $request The current request. |
||
136 | */ |
||
137 | $prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request ); |
||
138 | |||
139 | // Can we use the cached call? |
||
140 | $use_cache = ! empty( $prepared_args['post'] ) |
||
|
|||
141 | && empty( $prepared_args['include'] ) |
||
142 | && empty( $prepared_args['exclude'] ) |
||
143 | && empty( $prepared_args['hide_empty'] ) |
||
144 | && empty( $prepared_args['search'] ) |
||
145 | && empty( $prepared_args['slug'] ); |
||
146 | |||
147 | if ( ! empty( $prepared_args['post'] ) ) { |
||
148 | $query_result = $this->get_terms_for_post( $prepared_args ); |
||
149 | $total_terms = $this->total_terms; |
||
150 | } else { |
||
151 | $query_result = get_terms( $this->taxonomy, $prepared_args ); |
||
152 | |||
153 | $count_args = $prepared_args; |
||
154 | unset( $count_args['number'] ); |
||
155 | unset( $count_args['offset'] ); |
||
156 | $total_terms = wp_count_terms( $this->taxonomy, $count_args ); |
||
157 | |||
158 | // Ensure we don't return results when offset is out of bounds |
||
159 | // see https://core.trac.wordpress.org/ticket/35935 |
||
160 | if ( $prepared_args['offset'] >= $total_terms ) { |
||
161 | $query_result = array(); |
||
162 | } |
||
163 | |||
164 | // wp_count_terms can return a falsy value when the term has no children |
||
165 | if ( ! $total_terms ) { |
||
166 | $total_terms = 0; |
||
167 | } |
||
168 | } |
||
169 | $response = array(); |
||
170 | foreach ( $query_result as $term ) { |
||
171 | $data = $this->prepare_item_for_response( $term, $request ); |
||
172 | $response[] = $this->prepare_response_for_collection( $data ); |
||
173 | } |
||
174 | |||
175 | $response = rest_ensure_response( $response ); |
||
176 | |||
177 | // Store pagation values for headers then unset for count query. |
||
178 | $per_page = (int) $prepared_args['number']; |
||
179 | $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); |
||
180 | |||
181 | $response->header( 'X-WP-Total', (int) $total_terms ); |
||
182 | $max_pages = ceil( $total_terms / $per_page ); |
||
183 | $response->header( 'X-WP-TotalPages', (int) $max_pages ); |
||
184 | |||
185 | $base = add_query_arg( $request->get_query_params(), rest_url( $this->namespace . '/' . $this->rest_base ) ); |
||
186 | View Code Duplication | if ( $page > 1 ) { |
|
187 | $prev_page = $page - 1; |
||
188 | if ( $prev_page > $max_pages ) { |
||
189 | $prev_page = $max_pages; |
||
190 | } |
||
191 | $prev_link = add_query_arg( 'page', $prev_page, $base ); |
||
192 | $response->link_header( 'prev', $prev_link ); |
||
193 | } |
||
194 | View Code Duplication | if ( $max_pages > $page ) { |
|
195 | $next_page = $page + 1; |
||
196 | $next_link = add_query_arg( 'page', $next_page, $base ); |
||
197 | $response->link_header( 'next', $next_link ); |
||
198 | } |
||
199 | |||
200 | return $response; |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Get the terms attached to a post. |
||
205 | * |
||
206 | * This is an alternative to `get_terms()` that uses `get_the_terms()` |
||
207 | * instead, which hits the object cache. There are a few things not |
||
208 | * supported, notably `include`, `exclude`. In `self::get_items()` these |
||
209 | * are instead treated as a full query. |
||
210 | * |
||
211 | * @param array $prepared_args Arguments for `get_terms()` |
||
212 | * @return array List of term objects. (Total count in `$this->total_terms`) |
||
213 | */ |
||
214 | protected function get_terms_for_post( $prepared_args ) { |
||
215 | $query_result = get_the_terms( $prepared_args['post'], $this->taxonomy ); |
||
216 | if ( empty( $query_result ) ) { |
||
217 | $this->total_terms = 0; |
||
218 | return array(); |
||
219 | } |
||
220 | |||
221 | // get_items() verifies that we don't have `include` set, and default |
||
222 | // ordering is by `name` |
||
223 | if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ) ) ) { |
||
224 | switch ( $prepared_args['orderby'] ) { |
||
225 | case 'id': |
||
226 | $this->sort_column = 'term_id'; |
||
227 | break; |
||
228 | |||
229 | case 'slug': |
||
230 | case 'term_group': |
||
231 | case 'description': |
||
232 | case 'count': |
||
233 | $this->sort_column = $prepared_args['orderby']; |
||
234 | break; |
||
235 | } |
||
236 | usort( $query_result, array( $this, 'compare_terms' ) ); |
||
237 | } |
||
238 | if ( strtolower( $prepared_args['order'] ) !== 'asc' ) { |
||
239 | $query_result = array_reverse( $query_result ); |
||
240 | } |
||
241 | |||
242 | // Pagination |
||
243 | $this->total_terms = count( $query_result ); |
||
244 | $query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] ); |
||
245 | |||
246 | return $query_result; |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * Comparison function for sorting terms by a column. |
||
251 | * |
||
252 | * Uses `$this->sort_column` to determine field to sort by. |
||
253 | * |
||
254 | * @param stdClass $left Term object. |
||
255 | * @param stdClass $right Term object. |
||
256 | * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left. |
||
257 | */ |
||
258 | protected function compare_terms( $left, $right ) { |
||
259 | $col = $this->sort_column; |
||
260 | $left_val = $left->$col; |
||
261 | $right_val = $right->$col; |
||
262 | |||
263 | if ( is_int( $left_val ) && is_int( $right_val ) ) { |
||
264 | return $left_val - $right_val; |
||
265 | } |
||
266 | |||
267 | return strcmp( $left_val, $right_val ); |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * Check if a given request has access to read a term. |
||
272 | * |
||
273 | * @param WP_REST_Request $request Full details about the request. |
||
274 | * @return WP_Error|boolean |
||
275 | */ |
||
276 | View Code Duplication | public function get_item_permissions_check( $request ) { |
|
286 | |||
287 | /** |
||
288 | * Get a single term from a taxonomy |
||
289 | * |
||
290 | * @param WP_REST_Request $request Full details about the request |
||
291 | * @return WP_REST_Request|WP_Error |
||
292 | */ |
||
293 | View Code Duplication | public function get_item( $request ) { |
|
294 | |||
295 | $term = get_term( (int) $request['id'], $this->taxonomy ); |
||
296 | if ( ! $term || $term->taxonomy !== $this->taxonomy ) { |
||
297 | return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) ); |
||
298 | } |
||
299 | if ( is_wp_error( $term ) ) { |
||
300 | return $term; |
||
301 | } |
||
302 | |||
303 | $response = $this->prepare_item_for_response( $term, $request ); |
||
304 | |||
305 | return rest_ensure_response( $response ); |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Check if a given request has access to create a term |
||
310 | * |
||
311 | * @param WP_REST_Request $request Full details about the request. |
||
312 | * @return WP_Error|boolean |
||
313 | */ |
||
314 | public function create_item_permissions_check( $request ) { |
||
327 | |||
328 | /** |
||
329 | * Create a single term for a taxonomy |
||
330 | * |
||
331 | * @param WP_REST_Request $request Full details about the request |
||
332 | * @return WP_REST_Request|WP_Error |
||
333 | */ |
||
334 | public function create_item( $request ) { |
||
386 | |||
387 | /** |
||
388 | * Check if a given request has access to update a term |
||
389 | * |
||
390 | * @param WP_REST_Request $request Full details about the request. |
||
391 | * @return WP_Error|boolean |
||
392 | */ |
||
393 | View Code Duplication | public function update_item_permissions_check( $request ) { |
|
411 | |||
412 | /** |
||
413 | * Update a single term from a taxonomy |
||
414 | * |
||
415 | * @param WP_REST_Request $request Full details about the request |
||
416 | * @return WP_REST_Request|WP_Error |
||
417 | */ |
||
418 | public function update_item( $request ) { |
||
457 | |||
458 | /** |
||
459 | * Check if a given request has access to delete a term |
||
460 | * |
||
461 | * @param WP_REST_Request $request Full details about the request. |
||
462 | * @return WP_Error|boolean |
||
463 | */ |
||
464 | View Code Duplication | public function delete_item_permissions_check( $request ) { |
|
478 | |||
479 | /** |
||
480 | * Delete a single term from a taxonomy |
||
481 | * |
||
482 | * @param WP_REST_Request $request Full details about the request |
||
483 | * @return WP_REST_Response|WP_Error |
||
484 | */ |
||
485 | public function delete_item( $request ) { |
||
514 | |||
515 | /** |
||
516 | * Prepare a single term for create or update |
||
517 | * |
||
518 | * @param WP_REST_Request $request Request object. |
||
519 | * @return object $prepared_term Term object. |
||
520 | */ |
||
521 | public function prepare_item_for_database( $request ) { |
||
560 | |||
561 | /** |
||
562 | * Prepare a single term output for response |
||
563 | * |
||
564 | * @param obj $item Term object |
||
565 | * @param WP_REST_Request $request |
||
566 | * @return WP_REST_Response $response |
||
567 | */ |
||
568 | public function prepare_item_for_response( $item, $request ) { |
||
616 | |||
617 | /** |
||
618 | * Prepare links for the request. |
||
619 | * |
||
620 | * @param object $term Term object. |
||
621 | * @return array Links for the given term. |
||
622 | */ |
||
623 | protected function prepare_links( $term ) { |
||
669 | |||
670 | /** |
||
671 | * Get the Term's schema, conforming to JSON Schema |
||
672 | * |
||
673 | * @return array |
||
674 | */ |
||
675 | public function get_item_schema() { |
||
744 | |||
745 | /** |
||
746 | * Get the query params for collections |
||
747 | * |
||
748 | * @return array |
||
749 | */ |
||
750 | public function get_collection_params() { |
||
830 | |||
831 | /** |
||
832 | * Check that the taxonomy is valid |
||
833 | * |
||
834 | * @param string |
||
835 | * @return WP_Error|boolean |
||
836 | */ |
||
837 | protected function check_is_taxonomy_allowed( $taxonomy ) { |
||
844 | } |
||
845 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.