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 DataTable 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 DataTable, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | class DataTable extends Widget { |
||
25 | |||
26 | protected $_searchField; |
||
27 | protected $_urls; |
||
28 | protected $_pagination; |
||
29 | protected $_hasCheckboxes; |
||
30 | protected $_compileParts; |
||
31 | |||
32 | public function run(JsUtils $js){ |
||
33 | if($this->_hasCheckboxes && isset($js)){ |
||
34 | $js->execOn("change", "#".$this->identifier." [name='selection[]']", " |
||
35 | var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true; |
||
36 | \$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}}); |
||
37 | if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');}"); |
||
38 | } |
||
39 | parent::run($js); |
||
40 | } |
||
41 | |||
42 | View Code Duplication | public function __construct($identifier,$model,$modelInstance=NULL) { |
|
|
|||
43 | parent::__construct($identifier, $model,$modelInstance); |
||
44 | $this->_instanceViewer=new InstanceViewer(); |
||
45 | $this->content=["table"=>new HtmlTable($identifier, 0,0)]; |
||
46 | $this->_toolbarPosition=PositionInTable::BEFORETABLE; |
||
47 | } |
||
48 | |||
49 | public function compile(JsUtils $js=NULL,&$view=NULL){ |
||
50 | $this->_instanceViewer->setInstance($this->_model); |
||
51 | $captions=$this->_instanceViewer->getCaptions(); |
||
52 | |||
53 | $table=$this->content["table"]; |
||
54 | |||
55 | if($this->_hasCheckboxes){ |
||
56 | $ck=new HtmlCheckbox("main-ck-".$this->identifier,""); |
||
57 | $ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);"); |
||
58 | $ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);"); |
||
59 | \array_unshift($captions, $ck); |
||
60 | } |
||
61 | |||
62 | $table->setRowCount(0, \sizeof($captions)); |
||
63 | $table->setHeaderValues($captions); |
||
64 | if(isset($this->_compileParts)) |
||
65 | $table->setCompileParts($this->_compileParts); |
||
66 | if(isset($this->_searchField)){ |
||
67 | if(isset($js)) |
||
68 | $this->_searchField->postOn("change", $this->_urls,"{'s':$(this).val()}","-#".$this->identifier." tbody",["preventDefault"=>false]); |
||
69 | } |
||
70 | |||
71 | $this->_generateContent($table); |
||
72 | |||
73 | if($this->_hasCheckboxes){ |
||
74 | if($table->hasPart("thead")) |
||
75 | $table->getHeader()->getCell(0, 0)->addToProperty("class","no-sort"); |
||
76 | } |
||
77 | |||
78 | if(isset($this->_pagination) && $this->_pagination->getVisible()){ |
||
79 | $this->_generatePagination($table); |
||
80 | } |
||
81 | if(isset($this->_toolbar)){ |
||
82 | $this->_setToolbarPosition($table, $captions); |
||
83 | } |
||
84 | $this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]); |
||
85 | return parent::compile($js,$view); |
||
86 | } |
||
87 | |||
88 | protected function _generateContent($table){ |
||
89 | $objects=$this->_modelInstance; |
||
90 | if(isset($this->_pagination)){ |
||
91 | $objects=$this->_pagination->getObjects($this->_modelInstance); |
||
92 | } |
||
93 | InstanceViewer::setIndex(0); |
||
94 | $table->fromDatabaseObjects($objects, function($instance){ |
||
95 | $this->_instanceViewer->setInstance($instance); |
||
96 | InstanceViewer::$index++; |
||
97 | $result= $this->_instanceViewer->getValues(); |
||
98 | if($this->_hasCheckboxes){ |
||
99 | $ck=new HtmlCheckbox("ck-".$this->identifier,""); |
||
100 | $field=$ck->getField(); |
||
101 | $field->setProperty("value",$this->_instanceViewer->getIdentifier()); |
||
102 | $field->setProperty("name", "selection[]"); |
||
103 | \array_unshift($result, $ck); |
||
104 | } |
||
105 | return $result; |
||
106 | }); |
||
107 | } |
||
108 | |||
109 | private function _generatePagination($table){ |
||
118 | |||
119 | protected function _setToolbarPosition($table,$captions=NULL){ |
||
131 | |||
132 | /** |
||
133 | * Associates a $callback function after the compilation of the field at $index position |
||
134 | * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position |
||
135 | * @param int $index postion of the compiled field |
||
136 | * @param callable $callback function called after the field compilation |
||
137 | * @return \Ajax\semantic\widgets\datatable\DataTable |
||
138 | */ |
||
139 | public function afterCompile($index,$callback){ |
||
143 | |||
144 | private function addToolbarRow($part,$table,$captions){ |
||
149 | |||
150 | public function getHtmlComponent(){ |
||
151 | return $this->content["table"]; |
||
152 | } |
||
153 | |||
154 | public function getUrls() { |
||
157 | |||
158 | public function setUrls($urls) { |
||
162 | |||
163 | public function paginate($items_per_page=10,$page=1){ |
||
166 | |||
167 | public function getHasCheckboxes() { |
||
170 | |||
171 | public function setHasCheckboxes($_hasCheckboxes) { |
||
175 | |||
176 | public function refresh($compileParts=["tbody"]){ |
||
180 | /** |
||
181 | * @param string $caption |
||
182 | * @param callable $callback |
||
183 | * @return callable |
||
184 | */ |
||
185 | private function getFieldButtonCallable($caption,$callback=null){ |
||
188 | |||
189 | /** |
||
190 | * @param mixed $object |
||
191 | * @param callable $callback |
||
192 | * @return callable |
||
193 | */ |
||
194 | private function getCallable($thisCallback,$parameters,$callback=null){ |
||
209 | |||
210 | /** |
||
211 | * @param string $caption |
||
212 | * @return HtmlButton |
||
213 | */ |
||
214 | private function getFieldButton($caption){ |
||
217 | |||
218 | /** |
||
219 | * Inserts a new Button for each row |
||
220 | * @param string $caption |
||
221 | * @param callable $callback |
||
222 | * @return \Ajax\semantic\widgets\datatable\DataTable |
||
223 | */ |
||
224 | public function addFieldButton($caption,$callback=null){ |
||
228 | |||
229 | /** |
||
230 | * Inserts a new Button for each row at col $index |
||
231 | * @param int $index |
||
232 | * @param string $caption |
||
233 | * @param callable $callback |
||
234 | * @return \Ajax\semantic\widgets\datatable\DataTable |
||
235 | */ |
||
236 | public function insertFieldButton($index,$caption,$callback=null){ |
||
240 | |||
241 | /** |
||
242 | * Inserts a new Button for each row in col at $index |
||
243 | * @param int $index |
||
244 | * @param string $caption |
||
245 | * @param callable $callback |
||
246 | * @return \Ajax\semantic\widgets\datatable\DataTable |
||
247 | */ |
||
248 | public function insertInFieldButton($index,$caption,$callback=null){ |
||
252 | |||
253 | private function addDefaultButton($icon,$class=null,$callback=null){ |
||
257 | |||
258 | private function insertDefaultButtonIn($index,$icon,$class=null,$callback=null){ |
||
262 | |||
263 | private function getDefaultButton($icon,$class=null){ |
||
270 | |||
271 | public function addDeleteButton($callback=null){ |
||
274 | |||
275 | public function addEditButton($callback=null){ |
||
278 | |||
279 | public function addEditDeleteButtons($callbackEdit=null,$callbackDelete=null){ |
||
285 | |||
286 | public function insertDeleteButtonIn($index,$callback=null){ |
||
289 | |||
290 | public function insertEditButtonIn($index,$callback=null){ |
||
293 | |||
294 | public function setSelectable(){ |
||
298 | |||
299 | public function addSearchInToolbar(){ |
||
302 | |||
303 | public function getSearchField(){ |
||
310 | |||
311 | public function setSortable($colIndex=NULL) { |
||
315 | |||
316 | /** |
||
317 | * The callback function called after the insertion of each row when fromDatabaseObjects is called |
||
318 | * callback function takes the parameters $row : the row inserted and $object: the instance of model used |
||
319 | * @param callable $callback |
||
320 | * @return DataTable |
||
321 | */ |
||
322 | public function onNewRow($callback) { |
||
326 | } |
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.