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
dieintroduces 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 withthrowat this point:These limitations lead to logical operators rarely being of use in current PHP code.