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_Nav_Menu_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_Nav_Menu_Setting, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 21 | class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { |
||
| 22 | |||
| 23 | const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/'; |
||
| 24 | |||
| 25 | const TAXONOMY = 'nav_menu'; |
||
| 26 | |||
| 27 | const TYPE = 'nav_menu'; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * Setting type. |
||
| 31 | * |
||
| 32 | * @since 4.3.0 |
||
| 33 | * @access public |
||
| 34 | * @var string |
||
| 35 | */ |
||
| 36 | public $type = self::TYPE; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * Default setting value. |
||
| 40 | * |
||
| 41 | * @since 4.3.0 |
||
| 42 | * @access public |
||
| 43 | * @var array |
||
| 44 | * |
||
| 45 | * @see wp_get_nav_menu_object() |
||
| 46 | */ |
||
| 47 | public $default = array( |
||
| 48 | 'name' => '', |
||
| 49 | 'description' => '', |
||
| 50 | 'parent' => 0, |
||
| 51 | 'auto_add' => false, |
||
| 52 | ); |
||
| 53 | |||
| 54 | /** |
||
| 55 | * Default transport. |
||
| 56 | * |
||
| 57 | * @since 4.3.0 |
||
| 58 | * @access public |
||
| 59 | * @var string |
||
| 60 | */ |
||
| 61 | public $transport = 'postMessage'; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * The term ID represented by this setting instance. |
||
| 65 | * |
||
| 66 | * A negative value represents a placeholder ID for a new menu not yet saved. |
||
| 67 | * |
||
| 68 | * @since 4.3.0 |
||
| 69 | * @access public |
||
| 70 | * @var int |
||
| 71 | */ |
||
| 72 | public $term_id; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Previous (placeholder) term ID used before creating a new menu. |
||
| 76 | * |
||
| 77 | * This value will be exported to JS via the {@see 'customize_save_response'} filter |
||
| 78 | * so that JavaScript can update the settings to refer to the newly-assigned |
||
| 79 | * term ID. This value is always negative to indicate it does not refer to |
||
| 80 | * a real term. |
||
| 81 | * |
||
| 82 | * @since 4.3.0 |
||
| 83 | * @access public |
||
| 84 | * @var int |
||
| 85 | * |
||
| 86 | * @see WP_Customize_Nav_Menu_Setting::update() |
||
| 87 | * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() |
||
| 88 | */ |
||
| 89 | public $previous_term_id; |
||
| 90 | |||
| 91 | /** |
||
| 92 | * Whether or not update() was called. |
||
| 93 | * |
||
| 94 | * @since 4.3.0 |
||
| 95 | * @access protected |
||
| 96 | * @var bool |
||
| 97 | */ |
||
| 98 | protected $is_updated = false; |
||
| 99 | |||
| 100 | /** |
||
| 101 | * Status for calling the update method, used in customize_save_response filter. |
||
| 102 | * |
||
| 103 | * See {@see 'customize_save_response'}. |
||
| 104 | * |
||
| 105 | * When status is inserted, the placeholder term ID is stored in `$previous_term_id`. |
||
| 106 | * When status is error, the error is stored in `$update_error`. |
||
| 107 | * |
||
| 108 | * @since 4.3.0 |
||
| 109 | * @access public |
||
| 110 | * @var string updated|inserted|deleted|error |
||
| 111 | * |
||
| 112 | * @see WP_Customize_Nav_Menu_Setting::update() |
||
| 113 | * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() |
||
| 114 | */ |
||
| 115 | public $update_status; |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Any error object returned by wp_update_nav_menu_object() when setting is updated. |
||
| 119 | * |
||
| 120 | * @since 4.3.0 |
||
| 121 | * @access public |
||
| 122 | * @var WP_Error |
||
| 123 | * |
||
| 124 | * @see WP_Customize_Nav_Menu_Setting::update() |
||
| 125 | * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() |
||
| 126 | */ |
||
| 127 | public $update_error; |
||
| 128 | |||
| 129 | /** |
||
| 130 | * Constructor. |
||
| 131 | * |
||
| 132 | * Any supplied $args override class property defaults. |
||
| 133 | * |
||
| 134 | * @since 4.3.0 |
||
| 135 | * @access public |
||
| 136 | * |
||
| 137 | * @param WP_Customize_Manager $manager Bootstrap Customizer instance. |
||
| 138 | * @param string $id An specific ID of the setting. Can be a |
||
| 139 | * theme mod or option name. |
||
| 140 | * @param array $args Optional. Setting arguments. |
||
| 141 | * |
||
| 142 | * @throws Exception If $id is not valid for this setting type. |
||
| 143 | */ |
||
| 144 | public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { |
||
| 157 | |||
| 158 | /** |
||
| 159 | * Get the instance data for a given widget setting. |
||
| 160 | * |
||
| 161 | * @since 4.3.0 |
||
| 162 | * @access public |
||
| 163 | * |
||
| 164 | * @see wp_get_nav_menu_object() |
||
| 165 | * |
||
| 166 | * @return array Instance data. |
||
| 167 | */ |
||
| 168 | public function value() { |
||
| 203 | |||
| 204 | /** |
||
| 205 | * Handle previewing the setting. |
||
| 206 | * |
||
| 207 | * @since 4.3.0 |
||
| 208 | * @since 4.4.0 Added boolean return value |
||
| 209 | * @access public |
||
| 210 | * |
||
| 211 | * @see WP_Customize_Manager::post_value() |
||
| 212 | * |
||
| 213 | * @return bool False if method short-circuited due to no-op. |
||
| 214 | */ |
||
| 215 | public function preview() { |
||
| 216 | if ( $this->is_previewed ) { |
||
| 217 | return false; |
||
| 218 | } |
||
| 219 | |||
| 220 | $undefined = new stdClass(); |
||
| 221 | $is_placeholder = ( $this->term_id < 0 ); |
||
| 222 | $is_dirty = ( $undefined !== $this->post_value( $undefined ) ); |
||
| 223 | if ( ! $is_placeholder && ! $is_dirty ) { |
||
| 224 | return false; |
||
| 225 | } |
||
| 226 | |||
| 227 | $this->is_previewed = true; |
||
| 228 | $this->_original_value = $this->value(); |
||
| 229 | $this->_previewed_blog_id = get_current_blog_id(); |
||
|
|
|||
| 230 | |||
| 231 | add_filter( 'wp_get_nav_menus', array( $this, 'filter_wp_get_nav_menus' ), 10, 2 ); |
||
| 232 | add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 ); |
||
| 233 | add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); |
||
| 234 | add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); |
||
| 235 | |||
| 236 | return true; |
||
| 237 | } |
||
| 238 | |||
| 239 | /** |
||
| 240 | * Filters the wp_get_nav_menus() result to ensure the inserted menu object is included, and the deleted one is removed. |
||
| 241 | * |
||
| 242 | * @since 4.3.0 |
||
| 243 | * @access public |
||
| 244 | * |
||
| 245 | * @see wp_get_nav_menus() |
||
| 246 | * |
||
| 247 | * @param array $menus An array of menu objects. |
||
| 248 | * @param array $args An array of arguments used to retrieve menu objects. |
||
| 249 | * @return array |
||
| 250 | */ |
||
| 251 | public function filter_wp_get_nav_menus( $menus, $args ) { |
||
| 297 | |||
| 298 | /** |
||
| 299 | * Temporary non-closure passing of orderby value to function. |
||
| 300 | * |
||
| 301 | * @since 4.3.0 |
||
| 302 | * @access protected |
||
| 303 | * @var string |
||
| 304 | * |
||
| 305 | * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus() |
||
| 306 | * @see WP_Customize_Nav_Menu_Setting::_sort_menus_by_orderby() |
||
| 307 | */ |
||
| 308 | protected $_current_menus_sort_orderby; |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Sort menu objects by the class-supplied orderby property. |
||
| 312 | * |
||
| 313 | * This is a workaround for a lack of closures. |
||
| 314 | * |
||
| 315 | * @since 4.3.0 |
||
| 316 | * @access protected |
||
| 317 | * @param object $menu1 |
||
| 318 | * @param object $menu2 |
||
| 319 | * @return int |
||
| 320 | * |
||
| 321 | * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus() |
||
| 322 | */ |
||
| 323 | protected function _sort_menus_by_orderby( $menu1, $menu2 ) { |
||
| 327 | |||
| 328 | /** |
||
| 329 | * Filters the wp_get_nav_menu_object() result to supply the previewed menu object. |
||
| 330 | * |
||
| 331 | * Requesting a nav_menu object by anything but ID is not supported. |
||
| 332 | * |
||
| 333 | * @since 4.3.0 |
||
| 334 | * @access public |
||
| 335 | * |
||
| 336 | * @see wp_get_nav_menu_object() |
||
| 337 | * |
||
| 338 | * @param object|null $menu_obj Object returned by wp_get_nav_menu_object(). |
||
| 339 | * @param string $menu_id ID of the nav_menu term. Requests by slug or name will be ignored. |
||
| 340 | * @return object|null |
||
| 341 | */ |
||
| 342 | public function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) { |
||
| 378 | |||
| 379 | /** |
||
| 380 | * Filters the nav_menu_options option to include this menu's auto_add preference. |
||
| 381 | * |
||
| 382 | * @since 4.3.0 |
||
| 383 | * @access public |
||
| 384 | * |
||
| 385 | * @param array $nav_menu_options Nav menu options including auto_add. |
||
| 386 | * @return array (Kaybe) modified nav menu options. |
||
| 387 | */ |
||
| 388 | public function filter_nav_menu_options( $nav_menu_options ) { |
||
| 402 | |||
| 403 | /** |
||
| 404 | * Sanitize an input. |
||
| 405 | * |
||
| 406 | * Note that parent::sanitize() erroneously does wp_unslash() on $value, but |
||
| 407 | * we remove that in this override. |
||
| 408 | * |
||
| 409 | * @since 4.3.0 |
||
| 410 | * @access public |
||
| 411 | * |
||
| 412 | * @param array $value The value to sanitize. |
||
| 413 | * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. |
||
| 414 | * Otherwise the sanitized value. |
||
| 415 | */ |
||
| 416 | public function sanitize( $value ) { |
||
| 448 | |||
| 449 | /** |
||
| 450 | * Storage for data to be sent back to client in customize_save_response filter. |
||
| 451 | * |
||
| 452 | * See {@see 'customize_save_response'}. |
||
| 453 | * |
||
| 454 | * @access protected |
||
| 455 | * @since 4.3.0 |
||
| 456 | * @var array |
||
| 457 | * |
||
| 458 | * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() |
||
| 459 | */ |
||
| 460 | protected $_widget_nav_menu_updates = array(); |
||
| 461 | |||
| 462 | /** |
||
| 463 | * Create/update the nav_menu term for this setting. |
||
| 464 | * |
||
| 465 | * Any created menus will have their assigned term IDs exported to the client |
||
| 466 | * via the {@see 'customize_save_response'} filter. Likewise, any errors will be exported |
||
| 467 | * to the client via the customize_save_response() filter. |
||
| 468 | * |
||
| 469 | * To delete a menu, the client can send false as the value. |
||
| 470 | * |
||
| 471 | * @since 4.3.0 |
||
| 472 | * @access protected |
||
| 473 | * |
||
| 474 | * @see wp_update_nav_menu_object() |
||
| 475 | * |
||
| 476 | * @param array|false $value { |
||
| 477 | * The value to update. Note that slug cannot be updated via wp_update_nav_menu_object(). |
||
| 478 | * If false, then the menu will be deleted entirely. |
||
| 479 | * |
||
| 480 | * @type string $name The name of the menu to save. |
||
| 481 | * @type string $description The term description. Default empty string. |
||
| 482 | * @type int $parent The id of the parent term. Default 0. |
||
| 483 | * @type bool $auto_add Whether pages will auto_add to this menu. Default false. |
||
| 484 | * } |
||
| 485 | * @return null|void |
||
| 486 | */ |
||
| 487 | protected function update( $value ) { |
||
| 590 | |||
| 591 | /** |
||
| 592 | * Updates a nav_menu_options array. |
||
| 593 | * |
||
| 594 | * @since 4.3.0 |
||
| 595 | * @access protected |
||
| 596 | * |
||
| 597 | * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options() |
||
| 598 | * @see WP_Customize_Nav_Menu_Setting::update() |
||
| 599 | * |
||
| 600 | * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ). |
||
| 601 | * @param int $menu_id The term ID for the given menu. |
||
| 602 | * @param bool $auto_add Whether to auto-add or not. |
||
| 603 | * @return array (Maybe) modified nav_menu_otions array. |
||
| 604 | */ |
||
| 605 | protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) { |
||
| 620 | |||
| 621 | /** |
||
| 622 | * Export data for the JS client. |
||
| 623 | * |
||
| 624 | * @since 4.3.0 |
||
| 625 | * @access public |
||
| 626 | * |
||
| 627 | * @see WP_Customize_Nav_Menu_Setting::update() |
||
| 628 | * |
||
| 629 | * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. |
||
| 630 | * @return array Export data. |
||
| 631 | */ |
||
| 632 | public function amend_customize_save_response( $data ) { |
||
| 656 | } |
||
| 657 |
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.