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 Complex_Field 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 Complex_Field, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | class Complex_Field extends Field { |
||
17 | |||
18 | /** |
||
19 | * Visual layout type constants |
||
20 | */ |
||
21 | const LAYOUT_GRID = 'grid'; // default |
||
22 | |||
23 | const LAYOUT_TABBED_HORIZONTAL = 'tabbed-horizontal'; |
||
24 | |||
25 | const LAYOUT_TABBED_VERTICAL = 'tabbed-vertical'; |
||
26 | |||
27 | const TYPE_PROPERTY = '_type'; |
||
28 | |||
29 | /** |
||
30 | * Default field value |
||
31 | * |
||
32 | * @var array |
||
33 | */ |
||
34 | protected $default_value = array(); |
||
35 | |||
36 | /** |
||
37 | * Complex field layout |
||
38 | * |
||
39 | * @var string static::LAYOUT_* constant |
||
40 | */ |
||
41 | protected $layout = self::LAYOUT_GRID; |
||
42 | |||
43 | /** |
||
44 | * Value tree describing the complex values and all groups with their child fields |
||
45 | * |
||
46 | * @var array |
||
47 | */ |
||
48 | protected $value_tree = array(); |
||
49 | |||
50 | /** |
||
51 | * Array of groups registered for this complex field |
||
52 | * |
||
53 | * @var array |
||
54 | */ |
||
55 | protected $groups = array(); |
||
56 | |||
57 | /** |
||
58 | * Minimum number of entries. -1 for no limit |
||
59 | * |
||
60 | * @var integer |
||
61 | */ |
||
62 | protected $values_min = -1; |
||
63 | |||
64 | /** |
||
65 | * Maximum number of entries. -1 for no limit |
||
66 | * |
||
67 | * @var integer |
||
68 | */ |
||
69 | protected $values_max = -1; |
||
70 | |||
71 | /** |
||
72 | * Default entry state - collapsed or not |
||
73 | * |
||
74 | * @var boolean |
||
75 | */ |
||
76 | protected $collapsed = false; |
||
77 | |||
78 | /** |
||
79 | * Entry labels |
||
80 | * These are translated in init() |
||
81 | * |
||
82 | * @var array |
||
83 | */ |
||
84 | public $labels = array( |
||
85 | 'singular_name' => 'Entry', |
||
86 | 'plural_name' => 'Entries', |
||
87 | ); |
||
88 | |||
89 | /** |
||
90 | * Create a field from a certain type with the specified label. |
||
91 | * |
||
92 | * @param string $type Field type |
||
93 | * @param string $name Field name |
||
94 | * @param string $label Field label |
||
95 | */ |
||
96 | public function __construct( $type, $name, $label ) { |
||
100 | |||
101 | /** |
||
102 | * Initialization tasks. |
||
103 | */ |
||
104 | public function init() { |
||
111 | |||
112 | /** |
||
113 | * Set array of hierarchy field names |
||
114 | */ |
||
115 | public function set_hierarchy( $hierarchy ) { |
||
119 | |||
120 | /** |
||
121 | * Propagate hierarchy to child fields |
||
122 | */ |
||
123 | public function update_child_hierarchy() { |
||
130 | |||
131 | /** |
||
132 | * Activate the field once the container is attached. |
||
133 | */ |
||
134 | public function activate() { |
||
141 | |||
142 | /** |
||
143 | * Set the datastore of this field and propagate it to children |
||
144 | * |
||
145 | * @param Datastore_Interface $datastore |
||
146 | * @param boolean $set_as_default |
||
147 | * @return object $this |
||
148 | */ |
||
149 | View Code Duplication | public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) { |
|
159 | |||
160 | /** |
||
161 | * Propagate the datastore down the hierarchy |
||
162 | * |
||
163 | * @param Datastore_Interface $datastore |
||
164 | * @param boolean $set_as_default |
||
165 | */ |
||
166 | protected function update_child_datastore( Datastore_Interface $datastore, $set_as_default = false ) { |
||
171 | |||
172 | /** |
||
173 | * Retrieve all groups of fields. |
||
174 | * |
||
175 | * @return array $fields |
||
176 | */ |
||
177 | public function get_fields() { |
||
188 | |||
189 | /** |
||
190 | * Add a set/group of fields. |
||
191 | * |
||
192 | * Accepted param variations: |
||
193 | * - array<Field> $fields |
||
194 | * - string $group_name, array<Field> $fields |
||
195 | * - string $group_name, string $group_label, array<Field> $fields |
||
196 | * |
||
197 | * @return $this |
||
198 | */ |
||
199 | public function add_fields() { |
||
242 | |||
243 | /** |
||
244 | * Retrieve the groups of this field. |
||
245 | * |
||
246 | * @return array |
||
247 | */ |
||
248 | public function get_group_names() { |
||
251 | |||
252 | /** |
||
253 | * Retrieve a group by its name. |
||
254 | * |
||
255 | * @param string $group_name Group name |
||
256 | * @return Group_Field $group_object Group object |
||
257 | */ |
||
258 | public function get_group_by_name( $group_name ) { |
||
269 | |||
270 | /** |
||
271 | * Set the group label Underscore template. |
||
272 | * |
||
273 | * @param string|callable $template |
||
274 | * @return $this |
||
275 | */ |
||
276 | public function set_header_template( $template ) { |
||
291 | |||
292 | /** |
||
293 | * Set the field labels. |
||
294 | * Currently supported values: |
||
295 | * - singular_name - the singular entry label |
||
296 | * - plural_name - the plural entries label |
||
297 | * |
||
298 | * @param array $labels Labels |
||
299 | */ |
||
300 | public function setup_labels( $labels ) { |
||
304 | |||
305 | /** |
||
306 | * Return a clone of a field with hierarchy settings applied |
||
307 | * |
||
308 | * @param Field $field |
||
309 | * @param Field $parent_field |
||
310 | * @param int $group_index |
||
311 | * @return Field |
||
312 | */ |
||
313 | public function get_clone_under_field_in_hierarchy( $field, $parent_field, $group_index = 0 ) { |
||
319 | |||
320 | protected function get_prefilled_group_fields( $group_fields, $group_values, $group_index ) { |
||
333 | |||
334 | protected function get_prefilled_groups( $value_tree ) { |
||
354 | |||
355 | /** |
||
356 | * Load the field value from an input array based on it's name. |
||
357 | * |
||
358 | * @param array $input Array of field names and values. |
||
359 | */ |
||
360 | public function set_value_from_input( $input ) { |
||
399 | |||
400 | /** |
||
401 | * Save all contained groups of fields. |
||
402 | */ |
||
403 | public function save() { |
||
426 | |||
427 | /** |
||
428 | * {@inheritDoc} |
||
429 | */ |
||
430 | public function get_formatted_value() { |
||
450 | |||
451 | /** |
||
452 | * Convert an externally-keyed value array ('_type' => ...) |
||
453 | * to an internally-keyed one ('value' => ...) |
||
454 | * |
||
455 | * @param mixed $value |
||
456 | * @return mixed |
||
457 | */ |
||
458 | protected function external_to_internal_value( $value ) { |
||
470 | |||
471 | /** |
||
472 | * {@inheritDoc} |
||
473 | */ |
||
474 | public function set_value( $value ) { |
||
483 | |||
484 | /** |
||
485 | * Return the full value tree of all groups and their fields |
||
486 | * |
||
487 | * @return mixed |
||
488 | */ |
||
489 | public function get_value_tree() { |
||
492 | |||
493 | /** |
||
494 | * Set the full value tree of all groups and their fields |
||
495 | * |
||
496 | * @see Internal Glossary in DEVELOPMENT.MD |
||
497 | */ |
||
498 | public function set_value_tree( $value_tree ) { |
||
501 | |||
502 | /** |
||
503 | * Returns an array that holds the field data, suitable for JSON representation. |
||
504 | * |
||
505 | * @param bool $load Should the value be loaded from the database or use the value from the current instance. |
||
506 | * @return array |
||
507 | */ |
||
508 | public function to_json( $load ) { |
||
555 | |||
556 | /** |
||
557 | * Modify the layout of this field. |
||
558 | * |
||
559 | * @param string $layout |
||
560 | */ |
||
561 | public function set_layout( $layout ) { |
||
580 | |||
581 | /** |
||
582 | * Get the minimum number of entries. |
||
583 | * |
||
584 | * @return int $min |
||
585 | */ |
||
586 | public function get_min() { |
||
589 | |||
590 | /** |
||
591 | * Set the minimum number of entries. |
||
592 | * |
||
593 | * @param int $min |
||
594 | */ |
||
595 | public function set_min( $min ) { |
||
599 | |||
600 | /** |
||
601 | * Get the maximum number of entries. |
||
602 | * |
||
603 | * @return int $max |
||
604 | */ |
||
605 | public function get_max() { |
||
608 | |||
609 | /** |
||
610 | * Set the maximum number of entries. |
||
611 | * |
||
612 | * @param int $max |
||
613 | */ |
||
614 | public function set_max( $max ) { |
||
618 | |||
619 | /** |
||
620 | * Get collapsed state |
||
621 | * |
||
622 | * @return bool |
||
623 | */ |
||
624 | public function get_collapsed() { |
||
627 | |||
628 | /** |
||
629 | * Change the groups initial collapse state. |
||
630 | * This state relates to the state of which the groups are rendered. |
||
631 | * |
||
632 | * @param bool $collapsed |
||
633 | */ |
||
634 | public function set_collapsed( $collapsed = true ) { |
||
638 | } |
||
639 |
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.