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_API_Server 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_API_Server, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 23 | class WC_API_Server { |
||
| 24 | |||
| 25 | const METHOD_GET = 1; |
||
| 26 | const METHOD_POST = 2; |
||
| 27 | const METHOD_PUT = 4; |
||
| 28 | const METHOD_PATCH = 8; |
||
| 29 | const METHOD_DELETE = 16; |
||
| 30 | |||
| 31 | const READABLE = 1; // GET |
||
| 32 | const CREATABLE = 2; // POST |
||
| 33 | const EDITABLE = 14; // POST | PUT | PATCH |
||
| 34 | const DELETABLE = 16; // DELETE |
||
| 35 | const ALLMETHODS = 31; // GET | POST | PUT | PATCH | DELETE |
||
| 36 | |||
| 37 | /** |
||
| 38 | * Does the endpoint accept a raw request body? |
||
| 39 | */ |
||
| 40 | const ACCEPT_RAW_DATA = 64; |
||
| 41 | |||
| 42 | /** Does the endpoint accept a request body? (either JSON or XML) */ |
||
| 43 | const ACCEPT_DATA = 128; |
||
| 44 | |||
| 45 | /** |
||
| 46 | * Should we hide this endpoint from the index? |
||
| 47 | */ |
||
| 48 | const HIDDEN_ENDPOINT = 256; |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Map of HTTP verbs to constants |
||
| 52 | * @var array |
||
| 53 | */ |
||
| 54 | public static $method_map = array( |
||
| 55 | 'HEAD' => self::METHOD_GET, |
||
| 56 | 'GET' => self::METHOD_GET, |
||
| 57 | 'POST' => self::METHOD_POST, |
||
| 58 | 'PUT' => self::METHOD_PUT, |
||
| 59 | 'PATCH' => self::METHOD_PATCH, |
||
| 60 | 'DELETE' => self::METHOD_DELETE, |
||
| 61 | ); |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Requested path (relative to the API root, wp-json.php) |
||
| 65 | * |
||
| 66 | * @var string |
||
| 67 | */ |
||
| 68 | public $path = ''; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Requested method (GET/HEAD/POST/PUT/PATCH/DELETE) |
||
| 72 | * |
||
| 73 | * @var string |
||
| 74 | */ |
||
| 75 | public $method = 'HEAD'; |
||
| 76 | |||
| 77 | /** |
||
| 78 | * Request parameters |
||
| 79 | * |
||
| 80 | * This acts as an abstraction of the superglobals |
||
| 81 | * (GET => $_GET, POST => $_POST) |
||
| 82 | * |
||
| 83 | * @var array |
||
| 84 | */ |
||
| 85 | public $params = array( 'GET' => array(), 'POST' => array() ); |
||
| 86 | |||
| 87 | /** |
||
| 88 | * Request headers |
||
| 89 | * |
||
| 90 | * @var array |
||
| 91 | */ |
||
| 92 | public $headers = array(); |
||
| 93 | |||
| 94 | /** |
||
| 95 | * Request files (matches $_FILES) |
||
| 96 | * |
||
| 97 | * @var array |
||
| 98 | */ |
||
| 99 | public $files = array(); |
||
| 100 | |||
| 101 | /** |
||
| 102 | * Request/Response handler, either JSON by default |
||
| 103 | * or XML if requested by client |
||
| 104 | * |
||
| 105 | * @var WC_API_Handler |
||
| 106 | */ |
||
| 107 | public $handler; |
||
| 108 | |||
| 109 | |||
| 110 | /** |
||
| 111 | * Setup class and set request/response handler |
||
| 112 | * |
||
| 113 | * @since 2.1 |
||
| 114 | * @param $path |
||
| 115 | * @return WC_API_Server |
||
| 116 | */ |
||
| 117 | public function __construct( $path ) { |
||
| 152 | |||
| 153 | /** |
||
| 154 | * Check authentication for the request |
||
| 155 | * |
||
| 156 | * @since 2.1 |
||
| 157 | * @return WP_User|WP_Error WP_User object indicates successful login, WP_Error indicates unsuccessful login |
||
| 158 | */ |
||
| 159 | View Code Duplication | public function check_authentication() { |
|
| 174 | |||
| 175 | /** |
||
| 176 | * Convert an error to an array |
||
| 177 | * |
||
| 178 | * This iterates over all error codes and messages to change it into a flat |
||
| 179 | * array. This enables simpler client behaviour, as it is represented as a |
||
| 180 | * list in JSON rather than an object/map |
||
| 181 | * |
||
| 182 | * @since 2.1 |
||
| 183 | * @param WP_Error $error |
||
| 184 | * @return array List of associative arrays with code and message keys |
||
| 185 | */ |
||
| 186 | View Code Duplication | protected function error_to_array( $error ) { |
|
| 195 | |||
| 196 | /** |
||
| 197 | * Handle serving an API request |
||
| 198 | * |
||
| 199 | * Matches the current server URI to a route and runs the first matching |
||
| 200 | * callback then outputs a JSON representation of the returned value. |
||
| 201 | * |
||
| 202 | * @since 2.1 |
||
| 203 | * @uses WC_API_Server::dispatch() |
||
| 204 | */ |
||
| 205 | View Code Duplication | public function serve_request() { |
|
| 250 | |||
| 251 | /** |
||
| 252 | * Retrieve the route map |
||
| 253 | * |
||
| 254 | * The route map is an associative array with path regexes as the keys. The |
||
| 255 | * value is an indexed array with the callback function/method as the first |
||
| 256 | * item, and a bitmask of HTTP methods as the second item (see the class |
||
| 257 | * constants). |
||
| 258 | * |
||
| 259 | * Each route can be mapped to more than one callback by using an array of |
||
| 260 | * the indexed arrays. This allows mapping e.g. GET requests to one callback |
||
| 261 | * and POST requests to another. |
||
| 262 | * |
||
| 263 | * Note that the path regexes (array keys) must have @ escaped, as this is |
||
| 264 | * used as the delimiter with preg_match() |
||
| 265 | * |
||
| 266 | * @since 2.1 |
||
| 267 | * @return array `'/path/regex' => array( $callback, $bitmask )` or `'/path/regex' => array( array( $callback, $bitmask ), ...)` |
||
| 268 | */ |
||
| 269 | View Code Duplication | public function get_routes() { |
|
| 288 | |||
| 289 | /** |
||
| 290 | * Match the request to a callback and call it |
||
| 291 | * |
||
| 292 | * @since 2.1 |
||
| 293 | * @return mixed The value returned by the callback, or a WP_Error instance |
||
| 294 | */ |
||
| 295 | View Code Duplication | public function dispatch() { |
|
| 376 | |||
| 377 | /** |
||
| 378 | * Sort parameters by order specified in method declaration |
||
| 379 | * |
||
| 380 | * Takes a callback and a list of available params, then filters and sorts |
||
| 381 | * by the parameters the method actually needs, using the Reflection API |
||
| 382 | * |
||
| 383 | * @since 2.1 |
||
| 384 | * @param callable|array $callback the endpoint callback |
||
| 385 | * @param array $provided the provided request parameters |
||
| 386 | * @return array |
||
| 387 | */ |
||
| 388 | protected function sort_callback_params( $callback, $provided ) { |
||
| 414 | |||
| 415 | /** |
||
| 416 | * Get the site index. |
||
| 417 | * |
||
| 418 | * This endpoint describes the capabilities of the site. |
||
| 419 | * |
||
| 420 | * @since 2.1 |
||
| 421 | * @return array Index entity |
||
| 422 | */ |
||
| 423 | public function get_index() { |
||
| 477 | |||
| 478 | /** |
||
| 479 | * Send a HTTP status code |
||
| 480 | * |
||
| 481 | * @since 2.1 |
||
| 482 | * @param int $code HTTP status |
||
| 483 | */ |
||
| 484 | public function send_status( $code ) { |
||
| 487 | |||
| 488 | /** |
||
| 489 | * Send a HTTP header |
||
| 490 | * |
||
| 491 | * @since 2.1 |
||
| 492 | * @param string $key Header key |
||
| 493 | * @param string $value Header value |
||
| 494 | * @param boolean $replace Should we replace the existing header? |
||
| 495 | */ |
||
| 496 | public function header( $key, $value, $replace = true ) { |
||
| 499 | |||
| 500 | /** |
||
| 501 | * Send a Link header |
||
| 502 | * |
||
| 503 | * @internal The $rel parameter is first, as this looks nicer when sending multiple |
||
| 504 | * |
||
| 505 | * @link http://tools.ietf.org/html/rfc5988 |
||
| 506 | * @link http://www.iana.org/assignments/link-relations/link-relations.xml |
||
| 507 | * |
||
| 508 | * @since 2.1 |
||
| 509 | * @param string $rel Link relation. Either a registered type, or an absolute URL |
||
| 510 | * @param string $link Target IRI for the link |
||
| 511 | * @param array $other Other parameters to send, as an associative array |
||
| 512 | */ |
||
| 513 | View Code Duplication | public function link_header( $rel, $link, $other = array() ) { |
|
| 529 | |||
| 530 | /** |
||
| 531 | * Send pagination headers for resources |
||
| 532 | * |
||
| 533 | * @since 2.1 |
||
| 534 | * @param WP_Query|WP_User_Query $query |
||
| 535 | */ |
||
| 536 | public function add_pagination_headers( $query ) { |
||
| 583 | |||
| 584 | /** |
||
| 585 | * Returns the request URL with the page query parameter set to the specified page |
||
| 586 | * |
||
| 587 | * @since 2.1 |
||
| 588 | * @param int $page |
||
| 589 | * @return string |
||
| 590 | */ |
||
| 591 | View Code Duplication | private function get_paginated_url( $page ) { |
|
| 604 | |||
| 605 | /** |
||
| 606 | * Retrieve the raw request entity (body) |
||
| 607 | * |
||
| 608 | * @since 2.1 |
||
| 609 | * @return string |
||
| 610 | */ |
||
| 611 | View Code Duplication | public function get_raw_data() { |
|
| 627 | |||
| 628 | /** |
||
| 629 | * Parse an RFC3339 datetime into a MySQl datetime |
||
| 630 | * |
||
| 631 | * Invalid dates default to unix epoch |
||
| 632 | * |
||
| 633 | * @since 2.1 |
||
| 634 | * @param string $datetime RFC3339 datetime |
||
| 635 | * @return string MySQl datetime (YYYY-MM-DD HH:MM:SS) |
||
| 636 | */ |
||
| 637 | View Code Duplication | public function parse_datetime( $datetime ) { |
|
| 659 | |||
| 660 | /** |
||
| 661 | * Format a unix timestamp or MySQL datetime into an RFC3339 datetime |
||
| 662 | * |
||
| 663 | * @since 2.1 |
||
| 664 | * @param int|string $timestamp unix timestamp or MySQL datetime |
||
| 665 | * @param bool $convert_to_utc |
||
| 666 | * @return string RFC3339 datetime |
||
| 667 | */ |
||
| 668 | View Code Duplication | public function format_datetime( $timestamp, $convert_to_utc = false ) { |
|
| 696 | |||
| 697 | /** |
||
| 698 | * Extract headers from a PHP-style $_SERVER array |
||
| 699 | * |
||
| 700 | * @since 2.1 |
||
| 701 | * @param array $server Associative array similar to $_SERVER |
||
| 702 | * @return array Headers extracted from the input |
||
| 703 | */ |
||
| 704 | View Code Duplication | public function get_headers($server) { |
|
| 720 | |||
| 721 | /** |
||
| 722 | * Check if the current request accepts a JSON response by checking the endpoint suffix (.json) or |
||
| 723 | * the HTTP ACCEPT header |
||
| 724 | * |
||
| 725 | * @since 2.1 |
||
| 726 | * @return bool |
||
| 727 | */ |
||
| 728 | View Code Duplication | private function is_json_request() { |
|
| 740 | |||
| 741 | /** |
||
| 742 | * Check if the current request accepts an XML response by checking the endpoint suffix (.xml) or |
||
| 743 | * the HTTP ACCEPT header |
||
| 744 | * |
||
| 745 | * @since 2.1 |
||
| 746 | * @return bool |
||
| 747 | */ |
||
| 748 | View Code Duplication | private function is_xml_request() { |
|
| 760 | } |
||
| 761 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.