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 Sensei_Settings_API 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 Sensei_Settings_API, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | class Sensei_Settings_API { |
||
13 | |||
14 | public $token; |
||
15 | public $page_slug; |
||
16 | public $name; |
||
17 | public $menu_label; |
||
18 | public $settings; |
||
19 | public $sections; |
||
20 | public $fields; |
||
21 | public $errors; |
||
22 | |||
23 | public $has_range; |
||
24 | public $has_imageselector; |
||
25 | public $has_tabs; |
||
26 | private $tabs; |
||
27 | public $settings_version; |
||
28 | |||
29 | /** |
||
30 | * Constructor. |
||
31 | * @access public |
||
32 | * @since 1.0.0 |
||
33 | */ |
||
34 | public function __construct () { |
||
35 | |||
36 | $this->token = 'woothemes-sensei-settings'; |
||
37 | $this->page_slug = 'woothemes-sensei-settings-api'; |
||
38 | |||
39 | $this->sections = array(); |
||
40 | $this->fields = array(); |
||
41 | $this->remaining_fields = array(); |
||
42 | $this->errors = array(); |
||
43 | |||
44 | $this->has_range = false; |
||
45 | $this->has_imageselector = false; |
||
46 | $this->has_tabs = false; |
||
47 | $this->tabs = array(); |
||
48 | $this->settings_version = ''; |
||
49 | |||
50 | } // End __construct() |
||
51 | |||
52 | /** |
||
53 | * Setup the settings screen and necessary functions. |
||
54 | * @access public |
||
55 | * @since 1.0.0 |
||
56 | * @return void |
||
57 | */ |
||
58 | public function register_hook_listener() { |
||
59 | |||
60 | add_action( 'admin_menu', array( $this, 'register_settings_screen' ), 60 ); |
||
61 | add_action( 'admin_init', array( $this, 'settings_fields' ) ); |
||
62 | add_action( 'init', array( $this, 'general_init' ), 5 ); |
||
63 | |||
64 | } // End setup_settings() |
||
65 | |||
66 | /** |
||
67 | * Initialise settings sections, settings fields and create tabs, if applicable. |
||
68 | * @access public |
||
69 | * @since 1.0.3 |
||
70 | * @return void |
||
71 | */ |
||
72 | public function general_init() { |
||
73 | $this->init_sections(); |
||
74 | $this->init_fields(); |
||
75 | $this->get_settings(); |
||
76 | if ( $this->has_tabs == true ) { |
||
77 | $this->create_tabs(); |
||
78 | } // End If Statement |
||
79 | } // End general_init() |
||
80 | |||
81 | /** |
||
82 | * Register the settings sections. |
||
83 | * @access public |
||
84 | * @since 1.0.0 |
||
85 | * @return void |
||
86 | */ |
||
87 | public function init_sections () { |
||
91 | |||
92 | /** |
||
93 | * Register the settings fields. |
||
94 | * @access public |
||
95 | * @since 1.0.0 |
||
96 | * @return void |
||
97 | */ |
||
98 | public function init_fields () { |
||
102 | |||
103 | /** |
||
104 | * Construct and output HTML markup for the settings tabs. |
||
105 | * @access public |
||
106 | * @since 1.1.0 |
||
107 | * @return void |
||
108 | */ |
||
109 | public function settings_tabs () { |
||
110 | |||
111 | if ( ! $this->has_tabs ) { return; } |
||
112 | |||
113 | if ( count( $this->tabs ) > 0 ) { |
||
114 | $html = ''; |
||
115 | |||
116 | $html .= '<ul id="settings-sections" class="subsubsub hide-if-no-js">' . "\n"; |
||
117 | |||
118 | $sections = array( |
||
119 | 'all' => array( 'href' => '#all', 'name' => __( 'All', 'woothemes-sensei' ), 'class' => 'current all tab' ) |
||
120 | ); |
||
121 | |||
122 | foreach ( $this->tabs as $k => $v ) { |
||
123 | $sections[$k] = array( 'href' => '#' . esc_attr( $k ), 'name' => esc_attr( $v['name'] ), 'class' => 'tab' ); |
||
124 | } |
||
125 | |||
126 | $count = 1; |
||
127 | foreach ( $sections as $k => $v ) { |
||
128 | $count++; |
||
129 | $html .= '<li><a href="' . $v['href'] . '"'; |
||
130 | View Code Duplication | if ( isset( $v['class'] ) && ( $v['class'] != '' ) ) { $html .= ' class="' . esc_attr( $v['class'] ) . '"'; } |
|
131 | $html .= '>' . esc_attr( $v['name'] ) . '</a>'; |
||
132 | if ( $count <= count( $sections ) ) { $html .= ' | '; } |
||
133 | $html .= '</li>' . "\n"; |
||
134 | } |
||
135 | |||
136 | $html .= '</ul><div class="clear"></div>' . "\n"; |
||
137 | |||
138 | echo $html; |
||
139 | } |
||
140 | } // End settings_tabs() |
||
141 | |||
142 | /** |
||
143 | * Create settings tabs based on the settings sections. |
||
144 | * @access private |
||
145 | * @since 1.1.0 |
||
146 | * @return void |
||
147 | */ |
||
148 | private function create_tabs () { |
||
149 | if ( count( $this->sections ) > 0 ) { |
||
150 | $tabs = array(); |
||
151 | foreach ( $this->sections as $k => $v ) { |
||
152 | $tabs[$k] = $v; |
||
153 | } |
||
154 | |||
155 | $this->tabs = $tabs; |
||
156 | } |
||
157 | } // End create_tabs() |
||
158 | |||
159 | /** |
||
160 | * Create settings sections. |
||
161 | * @access public |
||
162 | * @since 1.0.0 |
||
163 | * @return void |
||
164 | */ |
||
165 | public function create_sections () { |
||
166 | if ( count( $this->sections ) > 0 ) { |
||
167 | foreach ( $this->sections as $k => $v ) { |
||
168 | add_settings_section( $k, $v['name'], array( $this, 'section_description' ), $this->token ); |
||
169 | } |
||
170 | } |
||
171 | } // End create_sections() |
||
172 | |||
173 | /** |
||
174 | * Create settings fields. |
||
175 | * @access public |
||
176 | * @since 1.0.0 |
||
177 | * @return void |
||
178 | */ |
||
179 | public function create_fields () { |
||
180 | if ( count( $this->sections ) > 0 ) { |
||
181 | // $this->parse_fields( $this->fields ); |
||
182 | |||
183 | foreach ( $this->fields as $k => $v ) { |
||
184 | $method = $this->determine_method( $v, 'form' ); |
||
185 | $name = $v['name']; |
||
186 | if ( $v['type'] == 'info' ) { $name = ''; } |
||
187 | add_settings_field( $k, $name, $method, $this->token, $v['section'], array( 'key' => $k, 'data' => $v ) ); |
||
188 | |||
189 | // Let the API know that we have a colourpicker field. |
||
190 | if ( $v['type'] == 'range' && $this->has_range == false ) { $this->has_range = true; } |
||
191 | } |
||
192 | } |
||
193 | } // End create_fields() |
||
194 | |||
195 | /** |
||
196 | * Determine the method to use for outputting a field, validating a field or checking a field. |
||
197 | * @access protected |
||
198 | * @since 1.0.0 |
||
199 | * @param array $data |
||
200 | * @return callable, array or string |
||
201 | */ |
||
202 | protected function determine_method ( $data, $type = 'form' ) { |
||
203 | $method = ''; |
||
204 | |||
205 | if ( ! in_array( $type, array( 'form', 'validate', 'check' ) ) ) { return; } |
||
206 | |||
207 | // Check for custom functions. |
||
208 | if ( isset( $data[$type] ) ) { |
||
209 | if ( function_exists( $data[$type] ) ) { |
||
210 | $method = $data[$type]; |
||
211 | } |
||
212 | |||
213 | if ( $method == '' && method_exists( $this, $data[$type] ) ) { |
||
214 | if ( $type == 'form' ) { |
||
215 | $method = array( $this, $data[$type] ); |
||
216 | } else { |
||
217 | $method = $data[$type]; |
||
218 | } |
||
219 | } |
||
220 | } |
||
221 | |||
222 | if ( $method == '' && method_exists ( $this, $type . '_field_' . $data['type'] ) ) { |
||
223 | if ( $type == 'form' ) { |
||
224 | $method = array( $this, $type . '_field_' . $data['type'] ); |
||
225 | } else { |
||
226 | $method = $type . '_field_' . $data['type']; |
||
227 | } |
||
228 | } |
||
229 | |||
230 | if ( $method == '' && function_exists ( $this->token . '_' . $type . '_field_' . $data['type'] ) ) { |
||
231 | $method = $this->token . '_' . $type . '_field_' . $data['type']; |
||
232 | } |
||
233 | |||
234 | if ( $method == '' ) { |
||
235 | if ( $type == 'form' ) { |
||
236 | $method = array( $this, $type . '_field_text' ); |
||
237 | } else { |
||
238 | $method = $type . '_field_text'; |
||
239 | } |
||
240 | } |
||
241 | |||
242 | return $method; |
||
243 | } // End determine_method() |
||
244 | |||
245 | /** |
||
246 | * Parse the fields into an array index on the sections property. |
||
247 | * @access public |
||
248 | * @since 1.0.0 |
||
249 | * @param array $fields |
||
250 | * @return void |
||
251 | */ |
||
252 | public function parse_fields ( $fields ) { |
||
253 | foreach ( $fields as $k => $v ) { |
||
254 | if ( isset( $v['section'] ) && ( $v['section'] != '' ) && ( isset( $this->sections[$v['section']] ) ) ) { |
||
255 | View Code Duplication | if ( ! isset( $this->sections[$v['section']]['fields'] ) ) { |
|
256 | $this->sections[$v['section']]['fields'] = array(); |
||
257 | } |
||
258 | |||
259 | $this->sections[$v['section']]['fields'][$k] = $v; |
||
260 | } else { |
||
261 | $this->remaining_fields[$k] = $v; |
||
262 | } |
||
263 | } |
||
264 | } // End parse_fields() |
||
265 | |||
266 | /** |
||
267 | * Register the settings screen within the WordPress admin. |
||
268 | * @access public |
||
269 | * @since 1.0.0 |
||
270 | * @return void |
||
271 | */ |
||
272 | public function register_settings_screen () { |
||
288 | |||
289 | /** |
||
290 | * The markup for the settings screen. |
||
291 | * @access public |
||
292 | * @since 1.0.0 |
||
293 | * @return void |
||
294 | */ |
||
295 | public function settings_screen () |
||
296 | { |
||
297 | |||
336 | |||
337 | /** |
||
338 | * Retrieve the settings from the database. |
||
339 | * @access public |
||
340 | * @since 1.0.0 |
||
341 | * @return array |
||
342 | */ |
||
343 | public function get_settings () { |
||
358 | |||
359 | /** |
||
360 | * Register the settings fields. |
||
361 | * @access public |
||
362 | * @since 1.0.0 |
||
363 | * @return void |
||
364 | */ |
||
365 | public function settings_fields () { |
||
370 | |||
371 | /** |
||
372 | * Display settings errors. |
||
373 | * @access public |
||
374 | * @since 1.0.0 |
||
375 | * @return void |
||
376 | */ |
||
377 | public function settings_errors () { |
||
380 | |||
381 | /** |
||
382 | * Display the description for a settings section. |
||
383 | * @access public |
||
384 | * @since 1.0.0 |
||
385 | * @return void |
||
386 | */ |
||
387 | public function section_description ( $section ) { |
||
392 | |||
393 | /** |
||
394 | * Generate text input field. |
||
395 | * @access public |
||
396 | * @since 1.0.0 |
||
397 | * @param array $args |
||
398 | * @return void |
||
399 | */ |
||
400 | View Code Duplication | public function form_field_text ( $args ) { |
|
408 | |||
409 | /** |
||
410 | * Generate color picker field. |
||
411 | * @access public |
||
412 | * @since 1.6.0 |
||
413 | * @param array $args |
||
414 | * @return void |
||
415 | */ |
||
416 | View Code Duplication | public function form_field_color ( $args ) { |
|
425 | |||
426 | /** |
||
427 | * Generate checkbox field. |
||
428 | * @access public |
||
429 | * @since 1.0.0 |
||
430 | * @param array $args |
||
431 | * @return void |
||
432 | */ |
||
433 | public function form_field_checkbox ( $args ) { |
||
451 | |||
452 | /** |
||
453 | * Generate textarea field. |
||
454 | * @access public |
||
455 | * @since 1.0.0 |
||
456 | * @param array $args |
||
457 | * @return void |
||
458 | */ |
||
459 | View Code Duplication | public function form_field_textarea ( $args ) { |
|
467 | |||
468 | /** |
||
469 | * Generate select box field. |
||
470 | * @access public |
||
471 | * @since 1.0.0 |
||
472 | * @param array $args |
||
473 | * @return void |
||
474 | */ |
||
475 | View Code Duplication | public function form_field_select ( $args ) { |
|
492 | |||
493 | /** |
||
494 | * Generate radio button field. |
||
495 | * @access public |
||
496 | * @since 1.0.0 |
||
497 | * @param array $args |
||
498 | * @return void |
||
499 | */ |
||
500 | View Code Duplication | public function form_field_radio ( $args ) { |
|
515 | |||
516 | /** |
||
517 | * Generate multicheck field. |
||
518 | * @access public |
||
519 | * @since 1.0.0 |
||
520 | * @param array $args |
||
521 | * @return void |
||
522 | */ |
||
523 | public function form_field_multicheck ( $args ) { |
||
548 | |||
549 | /** |
||
550 | * Generate range field. |
||
551 | * @access public |
||
552 | * @since 1.0.0 |
||
553 | * @param array $args |
||
554 | * @return void |
||
555 | */ |
||
556 | View Code Duplication | public function form_field_range ( $args ) { |
|
573 | |||
574 | /** |
||
575 | * Generate image-based selector form field. |
||
576 | * @access public |
||
577 | * @since 1.0.0 |
||
578 | * @param array $args |
||
579 | * @return void |
||
580 | */ |
||
581 | View Code Duplication | public function form_field_images ( $args ) { |
|
596 | |||
597 | /** |
||
598 | * Generate information box field. |
||
599 | * @access public |
||
600 | * @since 1.0.0 |
||
601 | * @param array $args |
||
602 | * @return void |
||
603 | */ |
||
604 | public function form_field_info ( $args ) { |
||
620 | |||
621 | |||
622 | /** |
||
623 | * Generate button field. |
||
624 | * @access public |
||
625 | * @since 1.9.0 |
||
626 | * @param array $args |
||
627 | */ |
||
628 | public function form_field_button( $args ) { |
||
639 | |||
640 | |||
641 | /** |
||
642 | * Validate registered settings fields. |
||
643 | * @access public |
||
644 | * @since 1.0.0 |
||
645 | * @param array $input |
||
646 | * @uses $this->parse_errors() |
||
647 | * @return array $options |
||
648 | */ |
||
649 | public function validate_fields ( $input ) { |
||
705 | |||
706 | /** |
||
707 | * Validate text fields. |
||
708 | * @access public |
||
709 | * @since 1.0.0 |
||
710 | * @param string $input |
||
711 | * @return string |
||
712 | */ |
||
713 | public function validate_field_text ( $input ) { |
||
716 | |||
717 | /** |
||
718 | * Validate checkbox fields. |
||
719 | * @access public |
||
720 | * @since 1.0.0 |
||
721 | * @param string $input |
||
722 | * @return string |
||
723 | */ |
||
724 | public function validate_field_checkbox ( $input ) { |
||
731 | |||
732 | /** |
||
733 | * Validate multicheck fields. |
||
734 | * @access public |
||
735 | * @since 1.0.0 |
||
736 | * @param string $input |
||
737 | * @return string |
||
738 | */ |
||
739 | public function validate_field_multicheck ( $input ) { |
||
746 | |||
747 | /** |
||
748 | * Validate range fields. |
||
749 | * @access public |
||
750 | * @since 1.0.0 |
||
751 | * @param string $input |
||
752 | * @return string |
||
753 | */ |
||
754 | public function validate_field_range ( $input ) { |
||
759 | |||
760 | /** |
||
761 | * Validate URL fields. |
||
762 | * @access public |
||
763 | * @since 1.0.0 |
||
764 | * @param string $input |
||
765 | * @return string |
||
766 | */ |
||
767 | public function validate_field_url ( $input ) { |
||
770 | |||
771 | /** |
||
772 | * Check and validate the input from text fields. |
||
773 | * @param string $input String of the value to be validated. |
||
774 | * @since 1.1.0 |
||
775 | * @return boolean Is the value valid? |
||
776 | */ |
||
777 | public function check_field_text ( $input ) { |
||
782 | |||
783 | /** |
||
784 | * Log an error internally, for processing later using $this->parse_errors(). |
||
785 | * @access protected |
||
786 | * @since 1.0.0 |
||
787 | * @param string $key |
||
788 | * @param array $data |
||
789 | * @return void |
||
790 | */ |
||
791 | protected function add_error ( $key, $data ) { |
||
799 | |||
800 | /** |
||
801 | * Parse logged errors. |
||
802 | * @access protected |
||
803 | * @since 1.0.0 |
||
804 | * @return void |
||
805 | */ |
||
806 | protected function parse_errors () { |
||
816 | |||
817 | /** |
||
818 | * Return an array of field types expecting an array value returned. |
||
819 | * @access protected |
||
820 | * @since 1.0.0 |
||
821 | * @return array |
||
822 | */ |
||
823 | protected function get_array_field_types () { |
||
826 | |||
827 | /** |
||
828 | * Load in JavaScripts where necessary. |
||
829 | * @access public |
||
830 | * @since 1.0.0 |
||
831 | * @return void |
||
832 | */ |
||
833 | public function enqueue_scripts () { |
||
851 | |||
852 | /** |
||
853 | * Load in CSS styles where necessary. |
||
854 | * @access public |
||
855 | * @since 1.0.0 |
||
856 | * @return void |
||
857 | */ |
||
858 | public function enqueue_styles () { |
||
867 | |||
868 | /** |
||
869 | * Load in CSS styles for field types where necessary. |
||
870 | * @access public |
||
871 | * @since 1.0.0 |
||
872 | * @return void |
||
873 | */ |
||
874 | public function enqueue_field_styles () { |
||
886 | } // End Class |
||
887 | |||
894 |
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.