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 Theme_Options_Container 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 Theme_Options_Container, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class Theme_Options_Container extends Container { |
||
12 | |||
13 | /** |
||
14 | * Array of registered page slugs to verify uniqueness with |
||
15 | * |
||
16 | * @var array |
||
17 | */ |
||
18 | protected static $registered_pages = array(); |
||
19 | |||
20 | /** |
||
21 | * Array of container settings |
||
22 | * |
||
23 | * @var array |
||
24 | */ |
||
25 | public $settings = array( |
||
26 | 'parent' => '', |
||
27 | 'file' => '', |
||
28 | 'icon' => '', |
||
29 | 'menu_position' => null, |
||
30 | 'menu_title' => null, |
||
31 | ); |
||
32 | |||
33 | /** |
||
34 | * {@inheritDoc} |
||
35 | */ |
||
36 | View Code Duplication | public function __construct( $id, $title, $type, $condition_collection, $condition_translator ) { |
|
|
|||
37 | parent::__construct( $id, $title, $type, $condition_collection, $condition_translator ); |
||
38 | |||
39 | if ( ! $this->get_datastore() ) { |
||
40 | $this->set_datastore( Datastore::make( 'theme_options' ), $this->has_default_datastore() ); |
||
41 | } |
||
42 | |||
43 | if ( apply_filters( 'carbon_fields_' . $type . '_container_admin_only_access', true, $title, $this ) ) { |
||
44 | $this->where( 'current_user_capability', '=', 'manage_options' ); |
||
45 | } |
||
46 | } |
||
47 | |||
48 | /** |
||
49 | * Sanitize a title to a filename |
||
50 | * |
||
51 | * @param string $title |
||
52 | * @param string $extension |
||
53 | * @return string |
||
54 | */ |
||
55 | protected function title_to_filename( $title, $extension ) { |
||
56 | $title = sanitize_file_name( $title ); |
||
57 | $title = strtolower( $title ); |
||
58 | $title = remove_accents( $title ); |
||
59 | $title = preg_replace( array( |
||
60 | '~\s+~', |
||
61 | '~[^\w\d-]+~u', |
||
62 | '~-+~', |
||
63 | ), array( |
||
64 | '-', |
||
65 | '-', |
||
66 | '-', |
||
67 | ), $title ); |
||
68 | return $title . $extension; |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * Attach container as a theme options page/subpage. |
||
73 | */ |
||
74 | public function init() { |
||
75 | $registered = $this->register_page(); |
||
76 | if ( $registered ) { |
||
77 | add_action( 'admin_menu', array( $this, '_attach' ) ); |
||
78 | } |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Checks whether the current save request is valid |
||
83 | * |
||
84 | * @return bool |
||
85 | */ |
||
86 | public function is_valid_save() { |
||
93 | |||
94 | /** |
||
95 | * Perform save operation after successful is_valid_save() check. |
||
96 | * The call is propagated to all fields in the container. |
||
97 | * |
||
98 | * @param mixed $user_data |
||
99 | */ |
||
100 | public function save( $user_data = null ) { |
||
101 | try { |
||
102 | parent::save( $user_data ); |
||
103 | } catch ( Incorrect_Syntax_Exception $e ) { |
||
104 | $this->errors[] = $e->getMessage(); |
||
105 | } |
||
106 | |||
107 | do_action( 'carbon_fields_' . $this->type . '_container_saved', $user_data, $this ); |
||
108 | |||
109 | if ( ! headers_sent() ) { |
||
110 | wp_redirect( add_query_arg( array( 'settings-updated' => 'true' ) ) ); |
||
111 | } |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * Get environment array for page request (in admin) |
||
116 | * |
||
117 | * @return array |
||
118 | */ |
||
119 | protected function get_environment_for_request() { |
||
122 | |||
123 | /** |
||
124 | * Perform checks whether the container should be attached during the current request |
||
125 | * |
||
126 | * @return bool True if the container is allowed to be attached |
||
127 | */ |
||
128 | public function is_valid_attach_for_request() { |
||
131 | |||
132 | /** |
||
133 | * Get environment array for object id |
||
134 | * |
||
135 | * @return array |
||
136 | */ |
||
137 | protected function get_environment_for_object( $object_id ) { |
||
140 | |||
141 | /** |
||
142 | * Check container attachment rules against object id |
||
143 | * |
||
144 | * @param int $object_id |
||
145 | * @return bool |
||
146 | */ |
||
147 | public function is_valid_attach_for_object( $object_id = null ) { |
||
150 | |||
151 | /** |
||
152 | * Add theme options container pages. |
||
153 | * Hook the container saving action. |
||
154 | */ |
||
155 | public function attach() { |
||
156 | // use the "read" capability because conditions will handle actual access and save capability checking |
||
157 | // before the attach() method is called |
||
158 | |||
159 | // Add menu page |
||
160 | if ( ! $this->settings['parent'] ) { |
||
161 | add_menu_page( |
||
162 | $this->title, |
||
163 | $this->settings['menu_title'] ? $this->settings['menu_title'] : $this->title, |
||
164 | 'read', |
||
165 | $this->get_page_file(), |
||
166 | array( $this, 'render' ), |
||
167 | $this->settings['icon'], |
||
168 | $this->settings['menu_position'] |
||
169 | ); |
||
170 | } |
||
171 | |||
172 | add_submenu_page( |
||
173 | $this->settings['parent'], |
||
174 | $this->title, |
||
175 | $this->settings['menu_title'] ? $this->settings['menu_title'] : $this->title, |
||
176 | 'read', |
||
177 | $this->get_page_file(), |
||
178 | array( $this, 'render' ) |
||
179 | ); |
||
180 | |||
181 | $page_hook = get_plugin_page_hookname( $this->get_page_file(), '' ); |
||
182 | add_action( 'load-' . $page_hook, array( $this, '_save' ) ); |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * Whether this container is currently viewed. |
||
187 | * |
||
188 | * @return boolean |
||
189 | */ |
||
190 | public function should_activate() { |
||
199 | |||
200 | /** |
||
201 | * Output the container markup |
||
202 | */ |
||
203 | public function render() { |
||
204 | $input = stripslashes_deep( $_GET ); |
||
205 | $request_settings_updated = isset( $input['settings-updated'] ) ? $input['settings-updated'] : ''; |
||
206 | if ( $request_settings_updated === 'true' ) { |
||
207 | $this->notifications[] = __( 'Settings saved.', 'carbon-fields' ); |
||
208 | } |
||
209 | |||
210 | include \Carbon_Fields\DIR . '/templates/Container/' . $this->type . '.php'; |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Register the page while making sure it is unique. |
||
215 | * |
||
216 | * @return boolean |
||
217 | */ |
||
218 | protected function register_page() { |
||
219 | $file = $this->get_page_file(); |
||
220 | $parent = $this->settings['parent']; |
||
221 | |||
222 | if ( ! $parent ) { |
||
223 | // Register top level page |
||
224 | if ( isset( static::$registered_pages[ $file ] ) ) { |
||
225 | Incorrect_Syntax_Exception::raise( 'Page "' . $file . '" already registered' ); |
||
226 | return false; |
||
227 | } |
||
228 | |||
229 | static::$registered_pages[ $file ] = array(); |
||
230 | return true; |
||
231 | } |
||
232 | |||
233 | // Register sub-page |
||
234 | if ( ! isset( static::$registered_pages[ $parent ] ) ) { |
||
235 | static::$registered_pages[ $parent ] = array(); |
||
236 | } |
||
237 | |||
238 | if ( in_array( $file, static::$registered_pages[ $parent ] ) ) { |
||
239 | Incorrect_Syntax_Exception::raise( 'Page "' . $file . '" with parent "' . $parent . '" is already registered. Please set a name for the container.' ); |
||
240 | return false; |
||
241 | } |
||
242 | |||
243 | static::$registered_pages[ $parent ][] = $file; |
||
244 | return true; |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * Change the parent theme options page of this container |
||
249 | * |
||
250 | * @param string|Theme_Options_Container $parent |
||
251 | * @return Container $this |
||
252 | */ |
||
253 | public function set_page_parent( $parent ) { |
||
262 | |||
263 | /** |
||
264 | * Get the theme options file name of this container. |
||
265 | * |
||
266 | * @return string |
||
267 | */ |
||
268 | public function get_page_file() { |
||
269 | if ( ! empty( $this->settings['file'] ) ) { |
||
270 | return $this->settings['file']; |
||
271 | } |
||
272 | return $this->title_to_filename( 'crb_' . $this->get_id(), '.php' ); |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * Set the theme options file name of this container. |
||
277 | * |
||
278 | * @param string $file |
||
279 | * @return Container $this |
||
280 | */ |
||
281 | public function set_page_file( $file ) { |
||
285 | |||
286 | /** |
||
287 | * Set the title of this container in the administration menu. |
||
288 | * |
||
289 | * @param string $title |
||
290 | * @return Container $this |
||
291 | */ |
||
292 | public function set_page_menu_title( $title ) { |
||
296 | |||
297 | /** |
||
298 | * Alias of the set_page_menu_position() method for backwards compatibility |
||
299 | * |
||
300 | * @param integer $position |
||
301 | * @return Container $this |
||
302 | */ |
||
303 | public function set_page_position( $position ) { |
||
306 | |||
307 | /** |
||
308 | * Set the page position of this container in the administration menu. |
||
309 | * |
||
310 | * @param integer $position |
||
311 | * @return Container $this |
||
312 | */ |
||
313 | public function set_page_menu_position( $position ) { |
||
317 | |||
318 | /** |
||
319 | * Set the icon of this theme options page. |
||
320 | * Applicable only for parent theme option pages. |
||
321 | * |
||
322 | * @param string $icon |
||
323 | * @return Container $this |
||
324 | */ |
||
325 | public function set_icon( $icon ) { |
||
329 | } |
||
330 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.