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_REST_API_V2_Endpoint_Admin_Menu 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_REST_API_V2_Endpoint_Admin_Menu, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | class WPCOM_REST_API_V2_Endpoint_Admin_Menu extends WP_REST_Controller { |
||
13 | |||
14 | /** |
||
15 | * Namespace prefix. |
||
16 | * |
||
17 | * @var string |
||
18 | */ |
||
19 | public $namespace = 'wpcom/v2'; |
||
20 | |||
21 | /** |
||
22 | * Endpoint base route. |
||
23 | * |
||
24 | * @var string |
||
25 | */ |
||
26 | public $rest_base = 'admin-menu'; |
||
27 | |||
28 | /** |
||
29 | * WPCOM_REST_API_V2_Endpoint_Admin_Menu constructor. |
||
30 | */ |
||
31 | public function __construct() { |
||
34 | |||
35 | /** |
||
36 | * Register routes. |
||
37 | */ |
||
38 | public function register_routes() { |
||
52 | |||
53 | /** |
||
54 | * Checks if a given request has access to admin menus. |
||
55 | * |
||
56 | * @param WP_REST_Request $request Full details about the request. |
||
57 | * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. |
||
58 | */ |
||
59 | public function get_item_permissions_check( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
||
70 | |||
71 | /** |
||
72 | * Retrieves the admin menu. |
||
73 | * |
||
74 | * @param WP_REST_Request $request Full details about the request. |
||
75 | * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. |
||
76 | */ |
||
77 | public function get_item( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
||
92 | |||
93 | /** |
||
94 | * Prepares the admin menu for the REST response. |
||
95 | * |
||
96 | * @param array $menu Admin menu. |
||
97 | * @return array Admin menu |
||
98 | */ |
||
99 | public function prepare_menu_for_response( array $menu ) { |
||
132 | |||
133 | /** |
||
134 | * Retrieves the admin menu's schema, conforming to JSON Schema. |
||
135 | * |
||
136 | * Note: if the shape of the API endpoint data changes it is important to also update |
||
137 | * the corresponding schema.js file. |
||
138 | * |
||
139 | * @see https://github.com/Automattic/wp-calypso/blob/ebde236ec9b21ea9621c0b0523bd5ea185523731/client/state/admin-menu/schema.js |
||
140 | * |
||
141 | * @return array Item schema data. |
||
142 | */ |
||
143 | public function get_item_schema() { |
||
204 | |||
205 | /** |
||
206 | * Sets up a menu item for consumption by Calypso. |
||
207 | * |
||
208 | * @param array $menu_item Menu item. |
||
209 | * @return array Prepared menu item. |
||
210 | */ |
||
211 | private function prepare_menu_item( array $menu_item ) { |
||
212 | global $submenu; |
||
213 | |||
214 | $current_user_can_access_menu = current_user_can( $menu_item[1] ); |
||
215 | $submenu_items = isset( $submenu[ $menu_item[2] ] ) ? array_values( $submenu[ $menu_item[2] ] ) : array(); |
||
216 | $has_first_menu_item = isset( $submenu_items[0] ); |
||
217 | |||
218 | // Exclude unauthorized menu items when the user does not have access to the menu and the first submenu item. |
||
219 | if ( ! $current_user_can_access_menu && $has_first_menu_item && ! current_user_can( $submenu_items[0][1] ) ) { |
||
220 | return array(); |
||
221 | } |
||
222 | |||
223 | // Exclude unauthorized menu items that don't have submenus. |
||
224 | if ( ! $current_user_can_access_menu && ! $has_first_menu_item ) { |
||
225 | return array(); |
||
226 | } |
||
227 | |||
228 | // Exclude hidden menu items. |
||
229 | if ( false !== strpos( $menu_item[4], 'hide-if-js' ) ) { |
||
230 | // Exclude submenu items as well. |
||
231 | if ( ! empty( $submenu[ $menu_item[2] ] ) ) { |
||
232 | // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited |
||
233 | $submenu[ $menu_item[2] ] = array(); |
||
234 | } |
||
235 | return array(); |
||
236 | } |
||
237 | |||
238 | // Handle menu separators. |
||
239 | if ( false !== strpos( $menu_item[4], 'wp-menu-separator' ) ) { |
||
240 | return array( |
||
241 | 'type' => 'separator', |
||
242 | ); |
||
243 | } |
||
244 | |||
245 | $url = $menu_item[2]; |
||
246 | $parent_slug = ''; |
||
247 | |||
248 | // If there are submenus, the parent menu should always link to the first submenu. |
||
249 | // @see https://core.trac.wordpress.org/browser/trunk/src/wp-admin/menu-header.php?rev=49193#L152. |
||
250 | if ( ! empty( $submenu[ $menu_item[2] ] ) ) { |
||
251 | $parent_slug = $url; |
||
252 | $first_submenu_item = reset( $submenu[ $menu_item[2] ] ); |
||
253 | $url = $first_submenu_item[2]; |
||
254 | } |
||
255 | |||
256 | $item = array( |
||
257 | 'icon' => $this->prepare_menu_item_icon( $menu_item[6] ), |
||
258 | 'slug' => sanitize_title_with_dashes( $menu_item[2] ), |
||
259 | 'title' => $menu_item[0], |
||
260 | 'type' => 'menu-item', |
||
261 | 'url' => $this->prepare_menu_item_url( $url, $parent_slug ), |
||
262 | ); |
||
263 | |||
264 | $parsed_item = $this->parse_menu_item( $item['title'] ); |
||
265 | if ( ! empty( $parsed_item ) ) { |
||
266 | $item = array_merge( $item, $parsed_item ); |
||
267 | } |
||
268 | |||
269 | return $item; |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * Sets up a submenu item for consumption by Calypso. |
||
274 | * |
||
275 | * @param array $submenu_item Submenu item. |
||
276 | * @param array $menu_item Menu item. |
||
277 | * @return array Prepared submenu item. |
||
278 | */ |
||
279 | private function prepare_submenu_item( array $submenu_item, array $menu_item ) { |
||
305 | |||
306 | /** |
||
307 | * Prepares a menu icon for consumption by Calypso. |
||
308 | * |
||
309 | * @param string $icon Menu icon. |
||
310 | * @return string |
||
311 | */ |
||
312 | private function prepare_menu_item_icon( $icon ) { |
||
327 | |||
328 | /** |
||
329 | * Prepares a menu item url for consumption by Calypso. |
||
330 | * |
||
331 | * @param string $url Menu slug. |
||
332 | * @param string $parent_slug Optional. Parent menu item slug. Default empty string. |
||
333 | * @return string |
||
334 | */ |
||
335 | private function prepare_menu_item_url( $url, $parent_slug = '' ) { |
||
383 | |||
384 | /** |
||
385 | * "Plugins", "Comments", "Updates" menu items have a count badge when there are updates available. |
||
386 | * This method parses that information, removes the associated markup and adds it to the response. |
||
387 | * |
||
388 | * Also sanitizes the titles from remaining unexpected markup. |
||
389 | * |
||
390 | * @param string $title Title to parse. |
||
391 | * @return array |
||
392 | */ |
||
393 | private function parse_menu_item( $title ) { |
||
428 | } |
||
429 | |||
431 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.