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 AbstractController 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 AbstractController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | abstract class AbstractController extends \hidev\base\Controller |
||
21 | { |
||
22 | protected $_before = []; |
||
23 | protected $_after = []; |
||
24 | protected $_make = ['load', 'save']; |
||
25 | |||
26 | /** |
||
27 | * @var array list of performed actions |
||
28 | */ |
||
29 | protected $_done = []; |
||
30 | |||
31 | /** |
||
32 | * {@inheritdoc} |
||
33 | */ |
||
34 | public function options($actionId) |
||
35 | { |
||
36 | return array_merge(parent::options($actionId), array_keys(Helper::getPublicVars(get_called_class()))); |
||
37 | } |
||
38 | |||
39 | 2 | public function perform($action = 'make') |
|
40 | { |
||
41 | return $this->runActions(['before', $action, 'after']); |
||
42 | 2 | } |
|
43 | |||
44 | public function actionBefore() |
||
45 | { |
||
46 | return $this->runRequests($this->getBefore()); |
||
47 | } |
||
48 | |||
49 | /** |
||
50 | * @return int|Response exit code |
||
51 | */ |
||
52 | public function actionMake() |
||
53 | { |
||
54 | return $this->runActions($this->getMake()); |
||
55 | } |
||
56 | |||
57 | public function actionAfter() |
||
58 | { |
||
59 | return $this->runRequests($this->getAfter()); |
||
60 | } |
||
61 | |||
62 | public function actionLoad() |
||
63 | { |
||
64 | Yii::trace("Loading nothing for '$this->id'"); |
||
65 | } |
||
66 | |||
67 | public function actionSave() |
||
68 | { |
||
69 | Yii::trace("Saving nothing for '$this->id'"); |
||
70 | } |
||
71 | |||
72 | public function setBefore($requests) |
||
73 | { |
||
74 | $this->_before = array_merge($this->getBefore(), $this->normalizeTasks($requests)); |
||
75 | } |
||
76 | |||
77 | 3 | public function getBefore() |
|
78 | { |
||
79 | 3 | return $this->_before; |
|
80 | } |
||
81 | |||
82 | public function setMake($requests) |
||
83 | { |
||
84 | $this->_make = array_merge($this->getMake(), $this->normalizeTasks($requests)); |
||
85 | } |
||
86 | |||
87 | 5 | public function getMake() |
|
88 | { |
||
89 | 5 | return $this->_make; |
|
90 | } |
||
91 | |||
92 | public function setAfter($requests) |
||
93 | { |
||
94 | $this->_after = array_merge($this->getAfter(), $this->normalizeTasks($requests)); |
||
95 | } |
||
96 | |||
97 | 3 | public function getAfter() |
|
98 | { |
||
99 | 3 | return $this->_after; |
|
100 | } |
||
101 | |||
102 | public function normalizeTasks($tasks) |
||
103 | { |
||
104 | if (!$tasks) { |
||
105 | return []; |
||
106 | } elseif (!is_array($tasks)) { |
||
107 | $tasks = [(string) $tasks => 1]; |
||
108 | } |
||
109 | $res = []; |
||
110 | foreach ($tasks as $dep => $enabled) { |
||
111 | $res[(string) (is_int($dep) ? $enabled : $dep)] = (bool) (is_int($dep) ? 1 : $enabled); |
||
112 | } |
||
113 | |||
114 | return $res; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Runs array of requests. Stops on failure and returns exit code. |
||
119 | * @param null|string|array $requests |
||
120 | * @return int|Response exit code |
||
121 | */ |
||
122 | View Code Duplication | public function runRequests($requests) |
|
|
|||
123 | { |
||
124 | foreach ($this->normalizeTasks($requests) as $request => $enabled) { |
||
125 | if ($enabled) { |
||
126 | $res = $this->runRequest($request); |
||
127 | if (static::isNotOk($res)) { |
||
128 | return $res; |
||
129 | } |
||
130 | } |
||
131 | } |
||
132 | |||
133 | return 0; |
||
134 | } |
||
135 | |||
136 | public function runRequest($request) |
||
137 | { |
||
138 | return $request === null ? null : $this->module->runRequest($request); |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * Is response NOT Ok. |
||
143 | * @param Response|int $res |
||
144 | * @return bool |
||
145 | */ |
||
146 | public static function isNotOk($res) |
||
147 | { |
||
148 | return is_object($res) ? $res->exitStatus : $res; |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Is response Ok. |
||
153 | * @param Response|int $res |
||
154 | * @return bool |
||
155 | */ |
||
156 | public static function isOk($res) |
||
157 | { |
||
158 | return !static::isNotOk($res); |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * Runs list of actions. |
||
163 | * TODO: think to redo with runRequests. |
||
164 | * @param null|string|array $actions |
||
165 | * @return int|Response exit code |
||
166 | */ |
||
167 | View Code Duplication | public function runActions($actions) |
|
168 | { |
||
169 | foreach ($this->normalizeTasks($actions) as $action => $enabled) { |
||
170 | if ($enabled) { |
||
171 | $res = $this->runAction($action); |
||
172 | if (static::isNotOk($res)) { |
||
173 | return $res; |
||
174 | } |
||
175 | } |
||
176 | } |
||
177 | |||
178 | return 0; |
||
179 | } |
||
180 | |||
181 | public function runAction($id, $params = []) |
||
182 | { |
||
183 | if ($this->isDone($id)) { |
||
184 | return; |
||
185 | } |
||
186 | $result = parent::runAction($id, $params); |
||
187 | $this->markDone($id); |
||
188 | |||
189 | return $result; |
||
190 | } |
||
191 | |||
192 | public function isDone($action, $timestamp = null) |
||
193 | { |
||
194 | if (isset($this->_done[$action])) { |
||
195 | Yii::trace("Already done: '$this->id/$action'"); |
||
196 | |||
197 | return true; |
||
198 | } |
||
199 | |||
200 | return false; |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Mark action as already done. |
||
205 | * |
||
206 | * @param string $action action id |
||
207 | * @param int $time microtime when action was done, false for action was not done |
||
208 | */ |
||
209 | public function markDone($action, $time = null) |
||
210 | { |
||
211 | $this->_done[$action] = ($time === null || $time === true) ? microtime(1) : $time; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Runs given binary with given arguments. Returns exit code. |
||
216 | * @param string $name |
||
217 | * @param array|string $args |
||
218 | * @return int exit code |
||
219 | */ |
||
220 | public function passthru($name, $args = []) |
||
221 | { |
||
222 | return $this->takeGoal('binaries')->passthruBinary($name, $args); |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Runs given binary with given arguments. Returns stdout array. |
||
227 | * @param string $name |
||
228 | * @param string $args |
||
229 | * @param bool $returnExitCode, default false |
||
230 | * @return array|int stdout or exitcode |
||
231 | */ |
||
232 | public function exec($name, $args = '', $returnExitCode = false) |
||
233 | { |
||
234 | return $this->takeGoal('binaries')->execBinary($name, $args, $returnExitCode); |
||
235 | } |
||
236 | |||
237 | public function execCode($name, $args = '') |
||
238 | { |
||
239 | return $this->takeGoal('binaries')->execBinary($name, $args, true); |
||
240 | } |
||
241 | |||
242 | public function readline($prompt) |
||
243 | { |
||
244 | return readline($prompt); |
||
245 | } |
||
246 | |||
247 | public function readpassword($prompt) |
||
248 | { |
||
249 | echo $prompt; |
||
250 | system('stty -echo'); |
||
251 | $password = rtrim(fgets(STDIN), PHP_EOL); |
||
252 | system('stty echo'); |
||
253 | echo "\n"; |
||
254 | |||
255 | return $password; |
||
256 | } |
||
257 | |||
258 | public function takeGoal($id) |
||
262 | |||
263 | public function takeConfig() |
||
267 | |||
268 | public function takeVendor() |
||
272 | |||
273 | public function takePackage() |
||
277 | |||
278 | public function takeVcs() |
||
282 | } |
||
283 |
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.