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 WPCOM_JSON_API 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 WPCOM_JSON_API, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
7 | class WPCOM_JSON_API { |
||
8 | static $self = null; |
||
9 | |||
10 | public $endpoints = array(); |
||
11 | |||
12 | public $token_details = array(); |
||
13 | |||
14 | public $method = ''; |
||
15 | public $url = ''; |
||
16 | public $path = ''; |
||
17 | public $version = null; |
||
18 | public $query = array(); |
||
19 | public $post_body = null; |
||
20 | public $files = null; |
||
21 | public $content_type = null; |
||
22 | public $accept = ''; |
||
23 | |||
24 | public $_server_https; |
||
25 | public $exit = true; |
||
26 | public $public_api_scheme = 'https'; |
||
27 | |||
28 | public $output_status_code = 200; |
||
29 | |||
30 | public $trapped_error = null; |
||
31 | public $did_output = false; |
||
32 | |||
33 | public $extra_headers = array(); |
||
34 | |||
35 | /** |
||
36 | * @return WPCOM_JSON_API instance |
||
37 | */ |
||
38 | static function init( $method = null, $url = null, $post_body = null ) { |
||
45 | |||
46 | function add( WPCOM_JSON_API_Endpoint $endpoint ) { |
||
57 | |||
58 | static function is_truthy( $value ) { |
||
68 | |||
69 | static function is_falsy( $value ) { |
||
79 | |||
80 | function __construct() { |
||
84 | |||
85 | function setup_inputs( $method = null, $url = null, $post_body = null ) { |
||
138 | |||
139 | function initialize() { |
||
142 | |||
143 | function serve( $exit = true ) { |
||
144 | ini_set( 'display_errors', false ); |
||
145 | |||
146 | $this->exit = (bool) $exit; |
||
147 | |||
148 | // This was causing problems with Jetpack, but is necessary for wpcom |
||
149 | // @see https://github.com/Automattic/jetpack/pull/2603 |
||
150 | // @see r124548-wpcom |
||
151 | if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { |
||
152 | add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 ); |
||
153 | } |
||
154 | |||
155 | add_filter( 'user_can_richedit', '__return_true' ); |
||
156 | |||
157 | add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) ); |
||
158 | |||
159 | $initialization = $this->initialize(); |
||
160 | if ( 'OPTIONS' == $this->method ) { |
||
161 | /** |
||
162 | * Fires before the page output. |
||
163 | * Can be used to specify custom header options. |
||
164 | * |
||
165 | * @module json-api |
||
166 | * |
||
167 | * @since 3.1.0 |
||
168 | */ |
||
169 | do_action( 'wpcom_json_api_options' ); |
||
170 | return $this->output( 200, '', 'text/plain' ); |
||
171 | } |
||
172 | |||
173 | if ( is_wp_error( $initialization ) ) { |
||
174 | $this->output_error( $initialization ); |
||
175 | return; |
||
176 | } |
||
177 | |||
178 | // Normalize path and extract API version |
||
179 | $this->path = untrailingslashit( $this->path ); |
||
180 | preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches ); |
||
181 | $this->path = substr( $this->path, strlen( $matches[0] ) ); |
||
182 | $this->version = $matches[1]; |
||
183 | |||
184 | $allowed_methods = array( 'GET', 'POST' ); |
||
185 | $four_oh_five = false; |
||
186 | |||
187 | $is_help = preg_match( '#/help/?$#i', $this->path ); |
||
188 | $matching_endpoints = array(); |
||
189 | |||
190 | if ( $is_help ) { |
||
191 | $origin = get_http_origin(); |
||
192 | |||
193 | if ( !empty( $origin ) && 'GET' == $this->method ) { |
||
194 | header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) ); |
||
195 | } |
||
196 | |||
197 | $this->path = substr( rtrim( $this->path, '/' ), 0, -5 ); |
||
198 | // Show help for all matching endpoints regardless of method |
||
199 | $methods = $allowed_methods; |
||
200 | $find_all_matching_endpoints = true; |
||
201 | // How deep to truncate each endpoint's path to see if it matches this help request |
||
202 | $depth = substr_count( $this->path, '/' ) + 1; |
||
203 | if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) { |
||
204 | $help_content_type = 'json'; |
||
205 | } else { |
||
206 | $help_content_type = 'html'; |
||
207 | } |
||
208 | } else { |
||
209 | if ( in_array( $this->method, $allowed_methods ) ) { |
||
210 | // Only serve requested method |
||
211 | $methods = array( $this->method ); |
||
212 | $find_all_matching_endpoints = false; |
||
213 | } else { |
||
214 | // We don't allow this requested method - find matching endpoints and send 405 |
||
215 | $methods = $allowed_methods; |
||
216 | $find_all_matching_endpoints = true; |
||
217 | $four_oh_five = true; |
||
218 | } |
||
219 | } |
||
220 | |||
221 | // Find which endpoint to serve |
||
222 | $found = false; |
||
223 | foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) { |
||
224 | $endpoint_path_versions = unserialize( $endpoint_path_versions ); |
||
225 | $endpoint_path = $endpoint_path_versions[0]; |
||
226 | $endpoint_min_version = $endpoint_path_versions[1]; |
||
227 | $endpoint_max_version = $endpoint_path_versions[2]; |
||
228 | |||
229 | // Make sure max_version is not less than min_version |
||
230 | if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) { |
||
231 | $endpoint_max_version = $endpoint_min_version; |
||
232 | } |
||
233 | |||
234 | foreach ( $methods as $method ) { |
||
235 | if ( !isset( $endpoints_by_method[$method] ) ) { |
||
236 | continue; |
||
237 | } |
||
238 | |||
239 | // Normalize |
||
240 | $endpoint_path = untrailingslashit( $endpoint_path ); |
||
241 | if ( $is_help ) { |
||
242 | // Truncate path at help depth |
||
243 | $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) ); |
||
244 | } |
||
245 | |||
246 | // Generate regular expression from sprintf() |
||
247 | $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path ); |
||
248 | |||
249 | if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) { |
||
250 | // This endpoint does not match the requested path. |
||
251 | continue; |
||
252 | } |
||
253 | |||
254 | if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) { |
||
255 | // This endpoint does not match the requested version. |
||
256 | continue; |
||
257 | } |
||
258 | |||
259 | $found = true; |
||
260 | |||
261 | if ( $find_all_matching_endpoints ) { |
||
262 | $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces ); |
||
263 | } else { |
||
264 | // The method parameters are now in $path_pieces |
||
265 | $endpoint = $endpoints_by_method[$method]; |
||
266 | break 2; |
||
267 | } |
||
268 | } |
||
269 | } |
||
270 | |||
271 | if ( !$found ) { |
||
272 | return $this->output( 404, '', 'text/plain' ); |
||
273 | } |
||
274 | |||
275 | if ( $four_oh_five ) { |
||
276 | $allowed_methods = array(); |
||
277 | foreach ( $matching_endpoints as $matching_endpoint ) { |
||
278 | $allowed_methods[] = $matching_endpoint[0]->method; |
||
279 | } |
||
280 | |||
281 | header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) ); |
||
282 | return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) ); |
||
283 | } |
||
284 | |||
285 | if ( $is_help ) { |
||
286 | /** |
||
287 | * Fires before the API output. |
||
288 | * |
||
289 | * @since 1.9.0 |
||
290 | * |
||
291 | * @param string help. |
||
292 | */ |
||
293 | do_action( 'wpcom_json_api_output', 'help' ); |
||
294 | $proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false; |
||
295 | if ( 'json' === $help_content_type ) { |
||
296 | $docs = array(); |
||
297 | foreach ( $matching_endpoints as $matching_endpoint ) { |
||
298 | if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) |
||
299 | $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) ); |
||
300 | } |
||
301 | return $this->output( 200, $docs ); |
||
302 | } else { |
||
303 | status_header( 200 ); |
||
304 | foreach ( $matching_endpoints as $matching_endpoint ) { |
||
305 | if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) |
||
306 | call_user_func( array( $matching_endpoint[0], 'document' ) ); |
||
307 | } |
||
308 | } |
||
309 | exit; |
||
310 | } |
||
311 | |||
312 | if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) { |
||
313 | return $this->output( 404, '', 'text/plain' ); |
||
314 | } |
||
315 | |||
316 | /** This action is documented in class.json-api.php */ |
||
317 | do_action( 'wpcom_json_api_output', $endpoint->stat ); |
||
318 | |||
319 | $response = $this->process_request( $endpoint, $path_pieces ); |
||
320 | |||
321 | if ( !$response && !is_array( $response ) ) { |
||
322 | return $this->output( 500, '', 'text/plain' ); |
||
323 | } elseif ( is_wp_error( $response ) ) { |
||
324 | return $this->output_error( $response ); |
||
325 | } |
||
326 | |||
327 | $output_status_code = $this->output_status_code; |
||
328 | $this->set_output_status_code(); |
||
329 | |||
330 | return $this->output( $output_status_code, $response, 'application/json', $this->extra_headers ); |
||
331 | } |
||
332 | |||
333 | function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) { |
||
337 | |||
338 | function output_early( $status_code, $response = null, $content_type = 'application/json' ) { |
||
350 | |||
351 | function set_output_status_code( $code = 200 ) { |
||
354 | |||
355 | function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) { |
||
435 | |||
436 | public static function serializable_error ( $error ) { |
||
461 | |||
462 | function output_error( $error ) { |
||
467 | |||
468 | function filter_fields( $response ) { |
||
529 | |||
530 | function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) { |
||
537 | |||
538 | function comment_edit_pre( $comment_content ) { |
||
541 | |||
542 | function json_encode( $data ) { |
||
545 | |||
546 | function ends_with( $haystack, $needle ) { |
||
549 | |||
550 | // Returns the site's blog_id in the WP.com ecosystem |
||
551 | function get_blog_id_for_output() { |
||
554 | |||
555 | // Returns the site's local blog_id |
||
556 | function get_blog_id( $blog_id ) { |
||
559 | |||
560 | function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) { |
||
571 | |||
572 | // Returns true if the specified blog ID is a restricted blog |
||
573 | function is_restricted_blog( $blog_id ) { |
||
586 | |||
587 | function post_like_count( $blog_id, $post_id ) { |
||
590 | |||
591 | function is_liked( $blog_id, $post_id ) { |
||
594 | |||
595 | function is_reblogged( $blog_id, $post_id ) { |
||
598 | |||
599 | function is_following( $blog_id ) { |
||
602 | |||
603 | function add_global_ID( $blog_id, $post_id ) { |
||
606 | |||
607 | function get_avatar_url( $email, $avatar_size = null ) { |
||
618 | |||
619 | /** |
||
620 | * traps `wp_die()` calls and outputs a JSON response instead. |
||
621 | * The result is always output, never returned. |
||
622 | * |
||
623 | * @param string|null $error_code Call with string to start the trapping. Call with null to stop. |
||
624 | * @param int $http_status HTTP status code, 400 by default. |
||
625 | */ |
||
626 | function trap_wp_die( $error_code = null, $http_status = 400 ) { |
||
653 | |||
654 | function wp_die_handler_callback() { |
||
657 | |||
658 | function wp_die_handler( $message, $title = '', $args = array() ) { |
||
690 | |||
691 | function output_trapped_error() { |
||
698 | |||
699 | function finish_request() { |
||
703 | } |
||
704 |
PHP has two types of connecting operators (logical operators, and boolean operators):
and
&&
or
||
The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like
&&
, or||
.Let’s take a look at a few examples:
Logical Operators are used for Control-Flow
One case where you explicitly want to use logical operators is for control-flow such as this:
Since
die
introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined withthrow
at this point:These limitations lead to logical operators rarely being of use in current PHP code.