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 FieldsBuilder 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 FieldsBuilder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
5 | class FieldsBuilder extends Builder implements NamedBuilder |
||
6 | { |
||
7 | protected $config = []; |
||
8 | protected $fieldManager; |
||
9 | protected $location = null; |
||
10 | protected $name; |
||
11 | |||
12 | public function __construct($name, $groupConfig = []) |
||
13 | { |
||
14 | $this->fieldManager = new FieldManager(); |
||
15 | $this->name = $name; |
||
16 | $this->setGroupConfig('key', $name); |
||
17 | $this->setGroupConfig('title', $this->generateLabel($name)); |
||
18 | |||
19 | $this->config = array_merge($this->config, $groupConfig); |
||
20 | } |
||
21 | |||
22 | public function setGroupConfig($key, $value) |
||
23 | { |
||
24 | $this->config[$key] = $value; |
||
25 | |||
26 | return $this; |
||
27 | } |
||
28 | |||
29 | public function getName() |
||
30 | { |
||
31 | return $this->name; |
||
32 | } |
||
33 | |||
34 | /** |
||
35 | * Namespace a group key |
||
36 | * Append the namespace 'group' before the set key. |
||
37 | * |
||
38 | * @param string $key Field Key |
||
39 | * @return string Field Key |
||
40 | */ |
||
41 | private function namespaceGroupKey($key) |
||
42 | { |
||
43 | if (strpos($key, 'group_') !== 0) { |
||
44 | $key = 'group_'.$key; |
||
45 | } |
||
46 | return $key; |
||
47 | } |
||
48 | |||
49 | /** |
||
50 | * Build the final config array. Build any other builders that may exist |
||
51 | * in the config. |
||
52 | * @return array final field config |
||
53 | */ |
||
54 | public function build() |
||
55 | { |
||
56 | return array_merge($this->config, [ |
||
57 | 'fields' => $this->buildFields(), |
||
58 | 'location' => $this->buildLocation(), |
||
59 | 'key' => $this->namespaceGroupKey($this->config['key']), |
||
60 | ]); |
||
61 | } |
||
62 | |||
63 | private function buildFields() |
||
64 | { |
||
65 | $fields = array_map(function($field) { |
||
66 | return ($field instanceof Builder) ? $field->build() : $field; |
||
67 | }, $this->getFields()); |
||
68 | |||
69 | return $this->transformFields($fields); |
||
70 | } |
||
71 | |||
72 | private function transformFields($fields) |
||
73 | { |
||
74 | $conditionalTransform = new Transform\ConditionalLogic($this); |
||
75 | $namespaceFieldKeyTransform = new Transform\NamespaceFieldKey($this); |
||
76 | |||
77 | return |
||
78 | $namespaceFieldKeyTransform->transform( |
||
79 | $conditionalTransform->transform($fields) |
||
80 | ); |
||
81 | } |
||
82 | |||
83 | private function buildLocation() |
||
84 | { |
||
85 | $location = $this->getLocation(); |
||
86 | return ($location instanceof Builder) ? $location->build() : $location; |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * Add multiple fields either via an array or from another builder |
||
91 | * @param mixed $fields array of fields or a FieldBuilder |
||
92 | */ |
||
93 | public function addFields($fields) |
||
94 | { |
||
95 | if ($fields instanceof FieldsBuilder) { |
||
96 | $builder = clone $fields; |
||
97 | $fields = $builder->getFields(); |
||
98 | } |
||
99 | |||
100 | foreach ($fields as $field) { |
||
101 | $this->getFieldManager()->pushField($field); |
||
102 | } |
||
103 | |||
104 | return $this; |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Add field to field group |
||
109 | * @param string $name field name |
||
110 | * @param array $args field options |
||
111 | * |
||
112 | * @throws FieldNameCollisionException if name already exists. |
||
113 | * |
||
114 | * @return $this |
||
115 | */ |
||
116 | public function addField($name, $args = []) |
||
117 | { |
||
118 | $field = array_merge([ |
||
119 | 'key' => $name, |
||
120 | 'name' => $name, |
||
121 | 'label' => $this->generateLabel($name), |
||
122 | ], $args); |
||
123 | |||
124 | $this->getFieldManager()->pushField($field); |
||
125 | return $this; |
||
126 | } |
||
127 | |||
128 | protected function addFieldType($name, $type, $args = []) |
||
129 | { |
||
130 | return $this->addField($name, array_merge([ |
||
131 | 'type' => $type, |
||
132 | ], $args)); |
||
133 | } |
||
134 | |||
135 | public function addText($name, $args = []) |
||
136 | { |
||
137 | return $this->addFieldType($name, 'text', $args); |
||
138 | } |
||
139 | |||
140 | public function addTextarea($name, $args = []) |
||
141 | { |
||
142 | return $this->addFieldType($name, 'textarea', $args); |
||
143 | } |
||
144 | |||
145 | public function addNumber($name, $args = []) |
||
146 | { |
||
147 | return $this->addFieldType($name, 'number', $args); |
||
148 | } |
||
149 | |||
150 | public function addEmail($name, $args = []) |
||
151 | { |
||
152 | return $this->addFieldType($name, 'email', $args); |
||
153 | } |
||
154 | |||
155 | public function addUrl($name, $args = []) |
||
156 | { |
||
157 | return $this->addFieldType($name, 'url', $args); |
||
158 | } |
||
159 | |||
160 | public function addPassword($name, $args = []) |
||
161 | { |
||
162 | return $this->addFieldType($name, 'password', $args); |
||
163 | } |
||
164 | |||
165 | public function addWysiwyg($name, $args = []) |
||
166 | { |
||
167 | return $this->addFieldType($name, 'wysiwyg', $args); |
||
168 | } |
||
169 | |||
170 | public function addOembed($name, $args = []) |
||
171 | { |
||
172 | return $this->addFieldType($name, 'oembed', $args); |
||
173 | } |
||
174 | |||
175 | public function addImage($name, $args = []) |
||
176 | { |
||
177 | return $this->addFieldType($name, 'image', $args); |
||
178 | } |
||
179 | |||
180 | public function addFile($name, $args = []) |
||
181 | { |
||
182 | return $this->addFieldType($name, 'file', $args); |
||
183 | } |
||
184 | |||
185 | public function addGallery($name, $args = []) |
||
186 | { |
||
187 | return $this->addFieldType($name, 'gallery', $args); |
||
188 | } |
||
189 | |||
190 | public function addTrueFalse($name, $args = []) |
||
191 | { |
||
192 | return $this->addFieldType($name, 'true_false', $args); |
||
193 | } |
||
194 | |||
195 | public function addSelect($name, $args = []) |
||
196 | { |
||
197 | return $this->addFieldType($name, 'select', $args); |
||
198 | } |
||
199 | |||
200 | public function addRadio($name, $args = []) |
||
201 | { |
||
202 | return $this->addFieldType($name, 'radio', $args); |
||
203 | } |
||
204 | |||
205 | public function addCheckbox($name, $args = []) |
||
206 | { |
||
207 | return $this->addFieldType($name, 'checkbox', $args); |
||
208 | } |
||
209 | |||
210 | public function addPostObject($name, $args = []) |
||
211 | { |
||
212 | return $this->addFieldType($name, 'post_object', $args); |
||
213 | } |
||
214 | |||
215 | public function addPostLink($name, $args = []) |
||
216 | { |
||
217 | return $this->addFieldType($name, 'post_link', $args); |
||
218 | } |
||
219 | |||
220 | public function addRelationship($name, $args = []) |
||
221 | { |
||
222 | return $this->addFieldType($name, 'relationship', $args); |
||
223 | } |
||
224 | |||
225 | public function addTaxonomy($name, $args = []) |
||
226 | { |
||
227 | return $this->addFieldType($name, 'taxonomy', $args); |
||
228 | } |
||
229 | |||
230 | public function addUser($name, $args = []) |
||
231 | { |
||
232 | return $this->addFieldType($name, 'user', $args); |
||
233 | } |
||
234 | |||
235 | public function addDatePicker($name, $args = []) |
||
236 | { |
||
237 | return $this->addFieldType($name, 'date_picker', $args); |
||
238 | } |
||
239 | |||
240 | public function addTimePicker($name, $args = []) |
||
241 | { |
||
242 | return $this->addFieldType($name, 'time_picker', $args); |
||
243 | } |
||
244 | |||
245 | public function addDateTimePicker($name, $args = []) |
||
246 | { |
||
247 | return $this->addFieldType($name, 'date_time_picker', $args); |
||
248 | } |
||
249 | |||
250 | public function addColorPicker($name, $args = []) |
||
254 | |||
255 | View Code Duplication | public function addTab($label, $args = []) |
|
264 | |||
265 | public function endpoint($value = 1) |
||
269 | |||
270 | View Code Duplication | public function addMessage($label, $message, $args = []) |
|
280 | |||
281 | public function addRepeater($name, $args = []) |
||
289 | |||
290 | public function addFlexibleContent($name, $args = []) |
||
298 | |||
299 | public function addChoice($choice, $label = null) |
||
311 | |||
312 | public function addChoices() |
||
313 | { |
||
314 | foreach (func_get_args() as $choice) { |
||
315 | if (is_array($choice)) { |
||
316 | $values = each($choice); |
||
317 | $this->addChoice($values['key'], $values['value']); |
||
318 | } else { |
||
319 | $this->addChoice($choice); |
||
320 | } |
||
321 | } |
||
322 | |||
323 | return $this; |
||
324 | } |
||
325 | |||
326 | public function conditional($name, $operator, $value) |
||
337 | |||
338 | protected function getFieldManager() |
||
339 | { |
||
340 | return $this->fieldManager; |
||
341 | } |
||
342 | |||
343 | public function getFields() |
||
344 | { |
||
345 | return $this->getFieldManager()->getFields(); |
||
346 | } |
||
347 | |||
348 | protected function getFieldIndex($name) |
||
349 | { |
||
350 | return $this->getFieldManager()->getFieldIndex($name); |
||
351 | } |
||
352 | |||
353 | public function getField($name) |
||
354 | { |
||
355 | return $this->getFieldManager()->getField($name); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Modify an already defined field |
||
360 | * @param string $name Name of the field |
||
361 | * @param mixed $modify Array of field configs or a closure that accepts |
||
362 | * a FieldsBuilder and returns a FieldsBuilder. |
||
363 | * |
||
364 | * @throws ModifyFieldReturnTypeException if $modify is a closure and doesn't |
||
365 | * return a FieldsBuilder. |
||
366 | * @throws FieldNotFoundException if the field name doesn't exist. |
||
367 | * |
||
368 | * @return FieldsBuilder $this |
||
369 | */ |
||
370 | public function modifyField($name, $modify) |
||
371 | { |
||
372 | if (is_array($modify)) { |
||
373 | $this->getFieldManager()->modifyField($name, $modify); |
||
374 | } else if ($modify instanceof \Closure) { |
||
375 | $field = $this->getField($name); |
||
376 | $index = $this->getFieldIndex($name); |
||
399 | |||
400 | /** |
||
401 | * Remove a field by name |
||
402 | * @param string $name Field to remove |
||
403 | * |
||
404 | * @return FieldsBuilder $this |
||
405 | */ |
||
406 | public function removeField($name) |
||
412 | |||
413 | public function defaultValue($value) |
||
417 | |||
418 | public function required($value = true) |
||
422 | |||
423 | public function instructions($value) |
||
427 | |||
428 | public function setConfig($key, $value) |
||
436 | |||
437 | public function setLocation($param, $operator, $value) |
||
448 | |||
449 | public function getLocation() |
||
453 | |||
454 | protected function generateLabel($name) |
||
458 | |||
459 | protected function generateName($name) |
||
463 | } |
||
464 |
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.