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 Column 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 Column, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
9 | class Column |
||
10 | { |
||
11 | /** |
||
12 | * @var Grid |
||
13 | */ |
||
14 | protected $grid; |
||
15 | |||
16 | /** |
||
17 | * Name of column. |
||
18 | * |
||
19 | * @var string |
||
20 | */ |
||
21 | protected $name; |
||
22 | |||
23 | /** |
||
24 | * Label of column. |
||
25 | * |
||
26 | * @var string |
||
27 | */ |
||
28 | protected $label; |
||
29 | |||
30 | /** |
||
31 | * Original value of column. |
||
32 | * |
||
33 | * @var mixed |
||
34 | */ |
||
35 | protected $original; |
||
36 | |||
37 | /** |
||
38 | * Is column sortable. |
||
39 | * |
||
40 | * @var bool |
||
41 | */ |
||
42 | protected $sortable = false; |
||
43 | |||
44 | /** |
||
45 | * Sort arguments. |
||
46 | * |
||
47 | * @var array |
||
48 | */ |
||
49 | protected $sort; |
||
50 | |||
51 | /** |
||
52 | * Attributes of column. |
||
53 | * |
||
54 | * @var array |
||
55 | */ |
||
56 | protected $attributes = []; |
||
57 | |||
58 | /** |
||
59 | * Value callback. |
||
60 | * |
||
61 | * @var \Closure |
||
62 | */ |
||
63 | protected $valueCallback; |
||
64 | |||
65 | /** |
||
66 | * Html callback. |
||
67 | * |
||
68 | * @var array |
||
69 | */ |
||
70 | protected $htmlCallbacks = []; |
||
71 | |||
72 | /** |
||
73 | * Relation name. |
||
74 | * |
||
75 | * @var bool |
||
76 | */ |
||
77 | protected $relation = false; |
||
78 | |||
79 | /** |
||
80 | * Relation column. |
||
81 | * |
||
82 | * @var string |
||
83 | */ |
||
84 | protected $relationColumn; |
||
85 | |||
86 | /** |
||
87 | * @param string $name |
||
88 | * @param string $label |
||
89 | */ |
||
90 | public function __construct($name, $label) |
||
96 | |||
97 | /** |
||
98 | * @param Grid $grid |
||
99 | */ |
||
100 | public function setGrid(Grid $grid) |
||
104 | |||
105 | /** |
||
106 | * Get name of this column. |
||
107 | * |
||
108 | * @return mixed |
||
109 | */ |
||
110 | public function getName() |
||
114 | |||
115 | /** |
||
116 | * Format label. |
||
117 | * |
||
118 | * @param $label |
||
119 | * |
||
120 | * @return mixed |
||
121 | */ |
||
122 | protected function formatLabel($label) |
||
128 | |||
129 | /** |
||
130 | * Get label of the column. |
||
131 | * |
||
132 | * @return mixed |
||
133 | */ |
||
134 | public function getLabel() |
||
138 | |||
139 | /** |
||
140 | * Set relation. |
||
141 | * |
||
142 | * @param $relation |
||
143 | * |
||
144 | * @return $this |
||
145 | */ |
||
146 | public function setRelation($relation) |
||
147 | { |
||
148 | $this->relation = $relation; |
||
149 | |||
150 | return $this; |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * If this column is relation column. |
||
155 | * |
||
156 | * @return bool |
||
157 | */ |
||
158 | protected function isRelation() |
||
159 | { |
||
160 | return (bool) $this->relation; |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * Mark this column as sortable. |
||
165 | * |
||
166 | * @return Column |
||
167 | */ |
||
168 | public function sortable() |
||
169 | { |
||
170 | $this->sortable = true; |
||
171 | |||
172 | return $this; |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * Wrap value with badge. |
||
177 | * |
||
178 | * @param string $style |
||
179 | * |
||
180 | * @return $this |
||
181 | */ |
||
182 | public function badge($style = 'red') |
||
183 | { |
||
184 | $callback = "<span class='badge bg-{$style}'>{value}</span>"; |
||
185 | |||
186 | $this->htmlCallback($callback); |
||
187 | |||
188 | return $this; |
||
189 | } |
||
190 | |||
191 | /** |
||
192 | * Wrap value with label. |
||
193 | * |
||
194 | * @param string $style |
||
195 | * |
||
196 | * @return $this |
||
197 | */ |
||
198 | public function label($style = 'success') |
||
199 | { |
||
200 | $callback = "<span class='label label-{$style}'>{value}</span>"; |
||
201 | |||
202 | $this->htmlCallback($callback); |
||
203 | |||
204 | return $this; |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Wrap value as a link. |
||
209 | * |
||
210 | * @param $href |
||
211 | * @param string $target |
||
212 | * |
||
213 | * @return $this |
||
214 | */ |
||
215 | public function link($href = '', $target = '_blank') |
||
216 | { |
||
217 | if (empty($href)) { |
||
218 | $href = '{$value}'; |
||
219 | } |
||
220 | |||
221 | $callback = "<a href='$href' target='$target'>{value}</a>"; |
||
222 | |||
223 | $this->htmlCallback($callback); |
||
224 | |||
225 | return $this; |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Wrap value as a button. |
||
230 | * |
||
231 | * @param string $style |
||
232 | * |
||
233 | * @return $this |
||
234 | */ |
||
235 | View Code Duplication | public function button($style = 'default') |
|
|
|||
236 | { |
||
237 | if (is_array($style)) { |
||
238 | $style = array_map(function ($style) { |
||
239 | return 'btn-'.$style; |
||
240 | }, $style); |
||
241 | |||
242 | $style = implode(' ', $style); |
||
243 | } elseif (is_string($style)) { |
||
244 | $style = 'btn-'.$style; |
||
245 | } |
||
246 | |||
247 | $callback = "<span class='btn $style'>{value}</span>"; |
||
248 | |||
249 | $this->htmlCallback($callback); |
||
250 | |||
251 | return $this; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Wrap value as a progressbar. |
||
256 | * |
||
257 | * @param string $style |
||
258 | * @param string $size |
||
259 | * @param int $max |
||
260 | * |
||
261 | * @return $this |
||
262 | */ |
||
263 | View Code Duplication | public function progressBar($style = 'primary', $size = 'sm', $max = 100) |
|
264 | { |
||
265 | if (is_array($style)) { |
||
266 | $style = array_map(function ($style) { |
||
267 | return 'progress-bar-'.$style; |
||
268 | }, $style); |
||
269 | |||
270 | $style = implode(' ', $style); |
||
271 | } elseif (is_string($style)) { |
||
272 | $style = 'progress-bar-'.$style; |
||
273 | } |
||
274 | |||
275 | $callback = <<<EOT |
||
276 | |||
277 | <div class="progress progress-$size"> |
||
278 | <div class="progress-bar $style" role="progressbar" aria-valuenow="{value}" aria-valuemin="0" aria-valuemax="$max" style="width: {value}%"> |
||
279 | <span class="sr-only">{value}</span> |
||
280 | </div> |
||
281 | </div> |
||
282 | |||
283 | EOT; |
||
284 | |||
285 | $this->htmlCallback($callback); |
||
286 | |||
287 | return $this; |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Wrap value as a image. |
||
292 | * |
||
293 | * @param string $server |
||
294 | * @param int $width |
||
295 | * @param int $height |
||
296 | * |
||
297 | * @return $this |
||
298 | */ |
||
299 | public function image($server = '', $width = 200, $height = 200) |
||
300 | { |
||
301 | $server = $server ?: config('admin.upload.host'); |
||
302 | |||
303 | $callback = "<img src='$server/{\$value}' style='max-width:{$width}px;max-height:{$height}px' class='img img-thumbnail' />"; |
||
304 | |||
305 | $this->htmlCallback($callback); |
||
306 | |||
307 | return $this; |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Make the column editable. |
||
312 | * |
||
313 | * @return $this |
||
314 | */ |
||
315 | public function editable() |
||
316 | { |
||
317 | $editable = new Editable($this->name, func_get_args()); |
||
318 | $editable->setResource($this->grid->resource()); |
||
319 | |||
320 | $this->htmlCallback($editable->html()); |
||
321 | |||
322 | return $this; |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Add a value callback. |
||
327 | * |
||
328 | * @param callable $callable |
||
329 | * |
||
330 | * @return $this |
||
331 | */ |
||
332 | public function value(Closure $callable) |
||
333 | { |
||
334 | $this->valueCallback = $callable->bindTo($this); |
||
335 | |||
336 | return $this; |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * Alias for value method. |
||
341 | * |
||
342 | * @param callable $callable |
||
343 | * |
||
344 | * @return $this |
||
345 | */ |
||
346 | public function display(Closure $callable) |
||
347 | { |
||
348 | return $this->value($callable); |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * If has a value callback. |
||
353 | * |
||
354 | * @return bool |
||
355 | */ |
||
356 | protected function hasValueCallback() |
||
357 | { |
||
358 | return (bool) $this->valueCallback; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Set html callback. |
||
363 | * |
||
364 | * @param $callback |
||
365 | */ |
||
366 | protected function htmlCallback($callback) |
||
367 | { |
||
368 | $this->htmlCallbacks[] = $callback; |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * If column has html callback. |
||
373 | * |
||
374 | * @return bool |
||
375 | */ |
||
376 | protected function hasHtmlCallback() |
||
377 | { |
||
378 | return !empty($this->htmlCallbacks); |
||
379 | } |
||
380 | |||
381 | /** |
||
382 | * Wrap value with callback. |
||
383 | * |
||
384 | * @param $value |
||
385 | * |
||
386 | * @return mixed |
||
387 | */ |
||
388 | protected function htmlWrap($value, $row = []) |
||
389 | { |
||
390 | foreach ($this->htmlCallbacks as $callback) { |
||
391 | $value = str_replace('{value}', $value, $callback); |
||
392 | } |
||
393 | |||
394 | $value = str_replace( |
||
395 | '{$value}', |
||
396 | is_null($this->original) ? 'NULL' : $this->htmlEntityEncode($this->original), |
||
397 | $value |
||
398 | ); |
||
399 | $value = str_replace('{pk}', array_get($row, $this->grid->getKeyName()), $value); |
||
400 | |||
401 | return $value; |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * Fill all data to every column. |
||
406 | * |
||
407 | * @param array $data |
||
408 | * |
||
409 | * @return mixed |
||
410 | */ |
||
411 | public function fill(array $data) |
||
412 | { |
||
413 | foreach ($data as &$item) { |
||
414 | $this->original = $value = array_get($item, $this->name); |
||
415 | |||
416 | $value = $this->htmlEntityEncode($value); |
||
417 | |||
418 | array_set($item, $this->name, $value); |
||
419 | |||
420 | if ($this->hasValueCallback()) { |
||
421 | $value = call_user_func($this->valueCallback, $this->original); |
||
422 | array_set($item, $this->name, $value); |
||
423 | } |
||
424 | |||
425 | if ($this->hasHtmlCallback()) { |
||
426 | $value = $this->htmlWrap($value, $item); |
||
427 | array_set($item, $this->name, $value); |
||
428 | } |
||
429 | } |
||
430 | |||
431 | return $data; |
||
432 | } |
||
433 | |||
434 | /** |
||
435 | * Convert characters to HTML entities recursively. |
||
436 | * |
||
437 | * @param array|string $item |
||
438 | * |
||
439 | * @return mixed |
||
440 | */ |
||
441 | protected function htmlEntityEncode($item) |
||
442 | { |
||
443 | if (is_array($item)) { |
||
444 | array_walk_recursive($item, function (&$value) { |
||
445 | $value = htmlentities($value); |
||
446 | }); |
||
447 | } else { |
||
448 | $item = htmlentities($item); |
||
449 | } |
||
450 | |||
451 | return $item; |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * Create the column sorter. |
||
456 | * |
||
457 | * @return string|void |
||
458 | */ |
||
459 | public function sorter() |
||
480 | |||
481 | /** |
||
482 | * Determine if this column is currently sorted. |
||
483 | * |
||
484 | * @return bool |
||
485 | */ |
||
486 | protected function isSorted() |
||
496 | |||
497 | /** |
||
498 | * @param string $method |
||
499 | * @param array $arguments |
||
500 | * |
||
501 | * @return $this |
||
502 | */ |
||
503 | public function __call($method, $arguments) |
||
514 | } |
||
515 |
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.