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_Customize_Setting 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_Customize_Setting, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 19 | class WP_Customize_Setting { |
||
| 20 | /** |
||
| 21 | * Customizer bootstrap instance. |
||
| 22 | * |
||
| 23 | * @since 3.4.0 |
||
| 24 | * @access public |
||
| 25 | * @var WP_Customize_Manager |
||
| 26 | */ |
||
| 27 | public $manager; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * Unique string identifier for the setting. |
||
| 31 | * |
||
| 32 | * @since 3.4.0 |
||
| 33 | * @access public |
||
| 34 | * @var string |
||
| 35 | */ |
||
| 36 | public $id; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * Type of customize settings. |
||
| 40 | * |
||
| 41 | * @since 3.4.0 |
||
| 42 | * @access public |
||
| 43 | * @var string |
||
| 44 | */ |
||
| 45 | public $type = 'theme_mod'; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * Capability required to edit this setting. |
||
| 49 | * |
||
| 50 | * @since 3.4.0 |
||
| 51 | * @access public |
||
| 52 | * @var string|array |
||
| 53 | */ |
||
| 54 | public $capability = 'edit_theme_options'; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * Feature a theme is required to support to enable this setting. |
||
| 58 | * |
||
| 59 | * @since 3.4.0 |
||
| 60 | * @access public |
||
| 61 | * @var string |
||
| 62 | */ |
||
| 63 | public $theme_supports = ''; |
||
| 64 | |||
| 65 | /** |
||
| 66 | * The default value for the setting. |
||
| 67 | * |
||
| 68 | * @since 3.4.0 |
||
| 69 | * @access public |
||
| 70 | * @var string |
||
| 71 | */ |
||
| 72 | public $default = ''; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Options for rendering the live preview of changes in Theme Customizer. |
||
| 76 | * |
||
| 77 | * Set this value to 'postMessage' to enable a custom Javascript handler to render changes to this setting |
||
| 78 | * as opposed to reloading the whole page. |
||
| 79 | * |
||
| 80 | * @link https://developer.wordpress.org/themes/customize-api |
||
| 81 | * |
||
| 82 | * @since 3.4.0 |
||
| 83 | * @access public |
||
| 84 | * @var string |
||
| 85 | */ |
||
| 86 | public $transport = 'refresh'; |
||
| 87 | |||
| 88 | /** |
||
| 89 | * Server-side validation callback for the setting's value. |
||
| 90 | * |
||
| 91 | * @since 4.6.0 |
||
| 92 | * @access public |
||
| 93 | * @var callable |
||
| 94 | */ |
||
| 95 | public $validate_callback = ''; |
||
| 96 | |||
| 97 | /** |
||
| 98 | * Callback to filter a Customize setting value in un-slashed form. |
||
| 99 | * |
||
| 100 | * @since 3.4.0 |
||
| 101 | * @access public |
||
| 102 | * @var callable |
||
| 103 | */ |
||
| 104 | public $sanitize_callback = ''; |
||
| 105 | |||
| 106 | /** |
||
| 107 | * Callback to convert a Customize PHP setting value to a value that is JSON serializable. |
||
| 108 | * |
||
| 109 | * @since 3.4.0 |
||
| 110 | * @access public |
||
| 111 | * @var string |
||
| 112 | */ |
||
| 113 | public $sanitize_js_callback = ''; |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Whether or not the setting is initially dirty when created. |
||
| 117 | * |
||
| 118 | * This is used to ensure that a setting will be sent from the pane to the |
||
| 119 | * preview when loading the Customizer. Normally a setting only is synced to |
||
| 120 | * the preview if it has been changed. This allows the setting to be sent |
||
| 121 | * from the start. |
||
| 122 | * |
||
| 123 | * @since 4.2.0 |
||
| 124 | * @access public |
||
| 125 | * @var bool |
||
| 126 | */ |
||
| 127 | public $dirty = false; |
||
| 128 | |||
| 129 | /** |
||
| 130 | * ID Data. |
||
| 131 | * |
||
| 132 | * @since 3.4.0 |
||
| 133 | * @access protected |
||
| 134 | * @var array |
||
| 135 | */ |
||
| 136 | protected $id_data = array(); |
||
| 137 | |||
| 138 | /** |
||
| 139 | * Whether or not preview() was called. |
||
| 140 | * |
||
| 141 | * @since 4.4.0 |
||
| 142 | * @access protected |
||
| 143 | * @var bool |
||
| 144 | */ |
||
| 145 | protected $is_previewed = false; |
||
| 146 | |||
| 147 | /** |
||
| 148 | * Cache of multidimensional values to improve performance. |
||
| 149 | * |
||
| 150 | * @since 4.4.0 |
||
| 151 | * @static |
||
| 152 | * @access protected |
||
| 153 | * @var array |
||
| 154 | */ |
||
| 155 | protected static $aggregated_multidimensionals = array(); |
||
| 156 | |||
| 157 | /** |
||
| 158 | * Whether the multidimensional setting is aggregated. |
||
| 159 | * |
||
| 160 | * @since 4.4.0 |
||
| 161 | * @access protected |
||
| 162 | * @var bool |
||
| 163 | */ |
||
| 164 | protected $is_multidimensional_aggregated = false; |
||
| 165 | |||
| 166 | /** |
||
| 167 | * Constructor. |
||
| 168 | * |
||
| 169 | * Any supplied $args override class property defaults. |
||
| 170 | * |
||
| 171 | * @since 3.4.0 |
||
| 172 | * |
||
| 173 | * @param WP_Customize_Manager $manager |
||
| 174 | * @param string $id An specific ID of the setting. Can be a |
||
| 175 | * theme mod or option name. |
||
| 176 | * @param array $args Setting arguments. |
||
| 177 | */ |
||
| 178 | public function __construct( $manager, $id, $args = array() ) { |
||
| 219 | |||
| 220 | /** |
||
| 221 | * Get parsed ID data for multidimensional setting. |
||
| 222 | * |
||
| 223 | * @since 4.4.0 |
||
| 224 | * |
||
| 225 | * @return array { |
||
| 226 | * ID data for multidimensional setting. |
||
| 227 | * |
||
| 228 | * @type string $base ID base |
||
| 229 | * @type array $keys Keys for multidimensional array. |
||
| 230 | * } |
||
| 231 | */ |
||
| 232 | final public function id_data() { |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Set up the setting for aggregated multidimensional values. |
||
| 238 | * |
||
| 239 | * When a multidimensional setting gets aggregated, all of its preview and update |
||
| 240 | * calls get combined into one call, greatly improving performance. |
||
| 241 | * |
||
| 242 | * @since 4.4.0 |
||
| 243 | */ |
||
| 244 | protected function aggregate_multidimensional() { |
||
| 263 | |||
| 264 | /** |
||
| 265 | * Reset `$aggregated_multidimensionals` static variable. |
||
| 266 | * |
||
| 267 | * This is intended only for use by unit tests. |
||
| 268 | * |
||
| 269 | * @since 4.5.0 |
||
| 270 | * @ignore |
||
| 271 | */ |
||
| 272 | static public function reset_aggregated_multidimensionals() { |
||
| 273 | self::$aggregated_multidimensionals = array(); |
||
| 274 | } |
||
| 275 | |||
| 276 | /** |
||
| 277 | * The ID for the current site when the preview() method was called. |
||
| 278 | * |
||
| 279 | * @since 4.2.0 |
||
| 280 | * @access protected |
||
| 281 | * @var int |
||
| 282 | */ |
||
| 283 | protected $_previewed_blog_id; |
||
| 284 | |||
| 285 | /** |
||
| 286 | * Return true if the current site is not the same as the previewed site. |
||
| 287 | * |
||
| 288 | * @since 4.2.0 |
||
| 289 | * |
||
| 290 | * @return bool If preview() has been called. |
||
| 291 | */ |
||
| 292 | public function is_current_blog_previewed() { |
||
| 298 | |||
| 299 | /** |
||
| 300 | * Original non-previewed value stored by the preview method. |
||
| 301 | * |
||
| 302 | * @see WP_Customize_Setting::preview() |
||
| 303 | * @since 4.1.1 |
||
| 304 | * @access protected |
||
| 305 | * @var mixed |
||
| 306 | */ |
||
| 307 | protected $_original_value; |
||
| 308 | |||
| 309 | /** |
||
| 310 | * Add filters to supply the setting's value when accessed. |
||
| 311 | * |
||
| 312 | * If the setting already has a pre-existing value and there is no incoming |
||
| 313 | * post value for the setting, then this method will short-circuit since |
||
| 314 | * there is no change to preview. |
||
| 315 | * |
||
| 316 | * @since 3.4.0 |
||
| 317 | * @since 4.4.0 Added boolean return value. |
||
| 318 | * |
||
| 319 | * @return bool False when preview short-circuits due no change needing to be previewed. |
||
| 320 | */ |
||
| 321 | public function preview() { |
||
| 422 | |||
| 423 | /** |
||
| 424 | * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated. |
||
| 425 | * |
||
| 426 | * This ensures that the new value will get sanitized and used the next time |
||
| 427 | * that `WP_Customize_Setting::_multidimensional_preview_filter()` |
||
| 428 | * is called for this setting. |
||
| 429 | * |
||
| 430 | * @since 4.4.0 |
||
| 431 | * |
||
| 432 | * @see WP_Customize_Manager::set_post_value() |
||
| 433 | * @see WP_Customize_Setting::_multidimensional_preview_filter() |
||
| 434 | */ |
||
| 435 | final public function _clear_aggregated_multidimensional_preview_applied_flag() { |
||
| 438 | |||
| 439 | /** |
||
| 440 | * Callback function to filter non-multidimensional theme mods and options. |
||
| 441 | * |
||
| 442 | * If switch_to_blog() was called after the preview() method, and the current |
||
| 443 | * site is now not the same site, then this method does a no-op and returns |
||
| 444 | * the original value. |
||
| 445 | * |
||
| 446 | * @since 3.4.0 |
||
| 447 | * |
||
| 448 | * @param mixed $original Old value. |
||
| 449 | * @return mixed New or old value. |
||
| 450 | */ |
||
| 451 | public function _preview_filter( $original ) { |
||
| 470 | |||
| 471 | /** |
||
| 472 | * Callback function to filter multidimensional theme mods and options. |
||
| 473 | * |
||
| 474 | * For all multidimensional settings of a given type, the preview filter for |
||
| 475 | * the first setting previewed will be used to apply the values for the others. |
||
| 476 | * |
||
| 477 | * @since 4.4.0 |
||
| 478 | * |
||
| 479 | * @see WP_Customize_Setting::$aggregated_multidimensionals |
||
| 480 | * @param mixed $original Original root value. |
||
| 481 | * @return mixed New or old value. |
||
| 482 | */ |
||
| 483 | final public function _multidimensional_preview_filter( $original ) { |
||
| 513 | |||
| 514 | /** |
||
| 515 | * Checks user capabilities and theme supports, and then saves |
||
| 516 | * the value of the setting. |
||
| 517 | * |
||
| 518 | * @since 3.4.0 |
||
| 519 | * |
||
| 520 | * @return false|void False if cap check fails or value isn't set or is invalid. |
||
| 521 | */ |
||
| 522 | final public function save() { |
||
| 545 | |||
| 546 | /** |
||
| 547 | * Fetch and sanitize the $_POST value for the setting. |
||
| 548 | * |
||
| 549 | * During a save request prior to save, post_value() provides the new value while value() does not. |
||
| 550 | * |
||
| 551 | * @since 3.4.0 |
||
| 552 | * |
||
| 553 | * @param mixed $default A default value which is used as a fallback. Default is null. |
||
| 554 | * @return mixed The default value on failure, otherwise the sanitized and validated value. |
||
| 555 | */ |
||
| 556 | final public function post_value( $default = null ) { |
||
| 559 | |||
| 560 | /** |
||
| 561 | * Sanitize an input. |
||
| 562 | * |
||
| 563 | * @since 3.4.0 |
||
| 564 | * |
||
| 565 | * @param string|array $value The value to sanitize. |
||
| 566 | * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid. |
||
| 567 | */ |
||
| 568 | public function sanitize( $value ) { |
||
| 580 | |||
| 581 | /** |
||
| 582 | * Validates an input. |
||
| 583 | * |
||
| 584 | * @since 4.6.0 |
||
| 585 | * |
||
| 586 | * @see WP_REST_Request::has_valid_params() |
||
| 587 | * |
||
| 588 | * @param mixed $value Value to validate. |
||
| 589 | * @return true|WP_Error True if the input was validated, otherwise WP_Error. |
||
| 590 | */ |
||
| 591 | public function validate( $value ) { |
||
| 592 | if ( is_wp_error( $value ) ) { |
||
| 593 | return $value; |
||
| 594 | } |
||
| 595 | if ( is_null( $value ) ) { |
||
| 596 | return new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); |
||
| 597 | } |
||
| 598 | |||
| 599 | $validity = new WP_Error(); |
||
| 600 | |||
| 601 | /** |
||
| 602 | * Validates a Customize setting value. |
||
| 603 | * |
||
| 604 | * Plugins should amend the `$validity` object via its `WP_Error::add()` method. |
||
| 605 | * |
||
| 606 | * The dynamic portion of the hook name, `$this->ID`, refers to the setting ID. |
||
| 607 | * |
||
| 608 | * @since 4.6.0 |
||
| 609 | * |
||
| 610 | * @param WP_Error $validity Filtered from `true` to `WP_Error` when invalid. |
||
| 611 | * @param mixed $value Value of the setting. |
||
| 612 | * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
||
| 613 | */ |
||
| 614 | $validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this ); |
||
| 615 | |||
| 616 | if ( is_wp_error( $validity ) && empty( $validity->errors ) ) { |
||
| 617 | $validity = true; |
||
| 618 | } |
||
| 619 | return $validity; |
||
| 620 | } |
||
| 621 | |||
| 622 | /** |
||
| 623 | * Get the root value for a setting, especially for multidimensional ones. |
||
| 624 | * |
||
| 625 | * @since 4.4.0 |
||
| 626 | * |
||
| 627 | * @param mixed $default Value to return if root does not exist. |
||
| 628 | * @return mixed |
||
| 629 | */ |
||
| 630 | protected function get_root_value( $default = null ) { |
||
| 645 | |||
| 646 | /** |
||
| 647 | * Set the root value for a setting, especially for multidimensional ones. |
||
| 648 | * |
||
| 649 | * @since 4.4.0 |
||
| 650 | * |
||
| 651 | * @param mixed $value Value to set as root of multidimensional setting. |
||
| 652 | * @return bool Whether the multidimensional root was updated successfully. |
||
| 653 | */ |
||
| 654 | protected function set_root_value( $value ) { |
||
| 674 | |||
| 675 | /** |
||
| 676 | * Save the value of the setting, using the related API. |
||
| 677 | * |
||
| 678 | * @since 3.4.0 |
||
| 679 | * |
||
| 680 | * @param mixed $value The value to update. |
||
| 681 | * @return bool The result of saving the value. |
||
| 682 | */ |
||
| 683 | protected function update( $value ) { |
||
| 711 | |||
| 712 | /** |
||
| 713 | * Deprecated method. |
||
| 714 | * |
||
| 715 | * @since 3.4.0 |
||
| 716 | * @deprecated 4.4.0 Deprecated in favor of update() method. |
||
| 717 | */ |
||
| 718 | protected function _update_theme_mod() { |
||
| 721 | |||
| 722 | /** |
||
| 723 | * Deprecated method. |
||
| 724 | * |
||
| 725 | * @since 3.4.0 |
||
| 726 | * @deprecated 4.4.0 Deprecated in favor of update() method. |
||
| 727 | */ |
||
| 728 | protected function _update_option() { |
||
| 731 | |||
| 732 | /** |
||
| 733 | * Fetch the value of the setting. |
||
| 734 | * |
||
| 735 | * @since 3.4.0 |
||
| 736 | * |
||
| 737 | * @return mixed The value. |
||
| 738 | */ |
||
| 739 | public function value() { |
||
| 784 | |||
| 785 | /** |
||
| 786 | * Sanitize the setting's value for use in JavaScript. |
||
| 787 | * |
||
| 788 | * @since 3.4.0 |
||
| 789 | * |
||
| 790 | * @return mixed The requested escaped value. |
||
| 791 | */ |
||
| 792 | public function js_value() { |
||
| 811 | |||
| 812 | /** |
||
| 813 | * Retrieves the data to export to the client via JSON. |
||
| 814 | * |
||
| 815 | * @since 4.6.0 |
||
| 816 | * |
||
| 817 | * @return array Array of parameters passed to JavaScript. |
||
| 818 | */ |
||
| 819 | public function json() { |
||
| 820 | return array( |
||
| 821 | 'value' => $this->js_value(), |
||
| 822 | 'transport' => $this->transport, |
||
| 823 | 'dirty' => $this->dirty, |
||
| 824 | 'type' => $this->type, |
||
| 825 | ); |
||
| 826 | } |
||
| 827 | |||
| 828 | /** |
||
| 829 | * Validate user capabilities whether the theme supports the setting. |
||
| 830 | * |
||
| 831 | * @since 3.4.0 |
||
| 832 | * |
||
| 833 | * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true. |
||
| 834 | */ |
||
| 835 | View Code Duplication | final public function check_capabilities() { |
|
| 844 | |||
| 845 | /** |
||
| 846 | * Multidimensional helper function. |
||
| 847 | * |
||
| 848 | * @since 3.4.0 |
||
| 849 | * |
||
| 850 | * @param $root |
||
| 851 | * @param $keys |
||
| 852 | * @param bool $create Default is false. |
||
| 853 | * @return array|void Keys are 'root', 'node', and 'key'. |
||
| 854 | */ |
||
| 855 | final protected function multidimensional( &$root, $keys, $create = false ) { |
||
| 894 | |||
| 895 | /** |
||
| 896 | * Will attempt to replace a specific value in a multidimensional array. |
||
| 897 | * |
||
| 898 | * @since 3.4.0 |
||
| 899 | * |
||
| 900 | * @param $root |
||
| 901 | * @param $keys |
||
| 902 | * @param mixed $value The value to update. |
||
| 903 | * @return mixed |
||
| 904 | */ |
||
| 905 | final protected function multidimensional_replace( $root, $keys, $value ) { |
||
| 918 | |||
| 919 | /** |
||
| 920 | * Will attempt to fetch a specific value from a multidimensional array. |
||
| 921 | * |
||
| 922 | * @since 3.4.0 |
||
| 923 | * |
||
| 924 | * @param $root |
||
| 925 | * @param $keys |
||
| 926 | * @param mixed $default A default value which is used as a fallback. Default is null. |
||
| 927 | * @return mixed The requested value or the default value. |
||
| 928 | */ |
||
| 929 | final protected function multidimensional_get( $root, $keys, $default = null ) { |
||
| 936 | |||
| 937 | /** |
||
| 938 | * Will attempt to check if a specific value in a multidimensional array is set. |
||
| 939 | * |
||
| 940 | * @since 3.4.0 |
||
| 941 | * |
||
| 942 | * @param $root |
||
| 943 | * @param $keys |
||
| 944 | * @return bool True if value is set, false if not. |
||
| 945 | */ |
||
| 946 | final protected function multidimensional_isset( $root, $keys ) { |
||
| 950 | } |
||
| 951 | |||
| 976 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.