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 UsersController 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 UsersController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
38 | class UsersController extends AppController |
||
39 | { |
||
40 | public $helpers = [ |
||
41 | 'SpectrumColorpicker.SpectrumColorpicker', |
||
42 | 'Posting', |
||
43 | 'Siezi/SimpleCaptcha.SimpleCaptcha', |
||
44 | 'Text' |
||
45 | ]; |
||
46 | |||
47 | /** |
||
48 | * Are moderators allowed to bloack users |
||
49 | * |
||
50 | * @var bool |
||
51 | */ |
||
52 | protected $modLocking = false; |
||
53 | |||
54 | /** |
||
55 | * {@inheritDoc} |
||
56 | */ |
||
57 | public function initialize() |
||
62 | |||
63 | /** |
||
64 | * Login user. |
||
65 | * |
||
66 | * @return void|\Cake\Network\Response |
||
67 | */ |
||
68 | public function login() |
||
123 | |||
124 | /** |
||
125 | * Logout user. |
||
126 | * |
||
127 | * @return void |
||
128 | */ |
||
129 | public function logout() |
||
140 | |||
141 | /** |
||
142 | * Register new user. |
||
143 | * |
||
144 | * @return void |
||
145 | */ |
||
146 | public function register() |
||
216 | |||
217 | /** |
||
218 | * register success (user clicked link in confirm mail) |
||
219 | * |
||
220 | * @param string $id user-ID |
||
221 | * @return void |
||
222 | * @throws BadRequestException |
||
223 | */ |
||
224 | public function rs($id = null) |
||
240 | |||
241 | /** |
||
242 | * Show list of all users. |
||
243 | * |
||
244 | * @return void |
||
245 | */ |
||
246 | public function index() |
||
247 | { |
||
248 | $menuItems = [ |
||
249 | 'username' => [__('username_marking'), []], |
||
250 | 'user_type' => [__('user_type'), []], |
||
251 | 'UserOnline.logged_in' => [ |
||
252 | __('userlist_online'), |
||
253 | ['direction' => 'desc'] |
||
254 | ], |
||
255 | 'registered' => [__('registered'), ['direction' => 'desc']] |
||
256 | ]; |
||
257 | $showBlocked = Configure::read('Saito.Settings.block_user_ui'); |
||
258 | if ($showBlocked) { |
||
259 | $menuItems['user_lock'] = [ |
||
260 | __('user.set.lock.t'), |
||
261 | ['direction' => 'desc'] |
||
262 | ]; |
||
263 | } |
||
264 | |||
265 | $this->paginate = $options = [ |
||
266 | 'contain' => ['UserOnline'], |
||
267 | 'sortWhitelist' => array_keys($menuItems), |
||
268 | 'finder' => 'paginated', |
||
269 | 'limit' => 400, |
||
270 | 'order' => [ |
||
271 | 'UserOnline.logged_in' => 'desc', |
||
272 | ] |
||
273 | ]; |
||
274 | $users = $this->paginate($this->Users); |
||
275 | |||
276 | $showBottomNavigation = true; |
||
277 | |||
278 | $this->set(compact('menuItems', 'showBottomNavigation', 'users')); |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Ignore user. |
||
283 | * |
||
284 | * @return void |
||
285 | */ |
||
286 | public function ignore() |
||
292 | |||
293 | /** |
||
294 | * Unignore user. |
||
295 | * |
||
296 | * @return void |
||
297 | */ |
||
298 | public function unignore() |
||
304 | |||
305 | /** |
||
306 | * Mark user as un-/ignored |
||
307 | * |
||
308 | * @param int $blockedId user to ignore |
||
309 | * @param bool $set block or unblock |
||
310 | * @return \Cake\Network\Response |
||
311 | */ |
||
312 | protected function _ignore($blockedId, $set) |
||
326 | |||
327 | /** |
||
328 | * Show user with profile $name |
||
329 | * |
||
330 | * @param string $name username |
||
331 | * @return void |
||
332 | */ |
||
333 | public function name($name = null) |
||
355 | |||
356 | /** |
||
357 | * View user profile. |
||
358 | * |
||
359 | * @param null $id user-ID |
||
360 | * @return \Cake\Network\Response|void |
||
361 | */ |
||
362 | public function view($id = null) |
||
363 | { |
||
364 | // redirect view/<username> to name/<username> |
||
365 | if (!empty($id) && !is_numeric($id)) { |
||
366 | $this->redirect( |
||
367 | ['controller' => 'users', 'action' => 'name', $id] |
||
368 | ); |
||
369 | |||
370 | return; |
||
371 | } |
||
372 | |||
373 | $user = $this->Users->find() |
||
374 | ->contain( |
||
375 | [ |
||
376 | 'UserBlocks' => function ($q) { |
||
377 | return $q->find('assocUsers'); |
||
378 | }, |
||
379 | 'UserOnline' |
||
380 | ] |
||
381 | ) |
||
382 | ->where(['Users.id' => $id]) |
||
383 | ->first(); |
||
384 | |||
385 | View Code Duplication | if ($id === null || empty($user)) { |
|
386 | $this->Flash->set(__('Invalid user'), ['element' => 'error']); |
||
387 | |||
388 | return $this->redirect('/'); |
||
389 | } |
||
390 | |||
391 | $entriesShownOnPage = 20; |
||
392 | $this->set( |
||
393 | 'lastEntries', |
||
394 | $this->Users->Entries->getRecentEntries( |
||
395 | $this->CurrentUser, |
||
396 | ['user_id' => $id, 'limit' => $entriesShownOnPage] |
||
397 | ) |
||
398 | ); |
||
399 | |||
400 | $this->set( |
||
401 | 'hasMoreEntriesThanShownOnPage', |
||
402 | ($user->numberOfPostings() - $entriesShownOnPage) > 0 |
||
403 | ); |
||
404 | |||
405 | if ($this->CurrentUser->isUser($id)) { |
||
406 | $ignores = $this->Users->UserIgnores->getAllIgnoredBy($id); |
||
407 | $user->set('ignores', $ignores); |
||
408 | } |
||
409 | |||
410 | $isEditingAllowed = $this->_isEditingAllowed($this->CurrentUser, $id); |
||
411 | |||
412 | $blockForm = new BlockForm(); |
||
413 | $solved = $this->Users->countSolved($id); |
||
414 | $this->set(compact('blockForm', 'isEditingAllowed', 'solved', 'user')); |
||
415 | $this->set('titleForLayout', $user->get('username')); |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * Set user avatar. |
||
420 | * |
||
421 | * @param string $userId user-ID |
||
422 | * @return void|\Cake\Network\Response |
||
423 | */ |
||
424 | public function avatar(int $userId) |
||
449 | |||
450 | /** |
||
451 | * Edit user. |
||
452 | * |
||
453 | * @param null $id user-ID |
||
454 | * |
||
455 | * @return \Cake\Network\Response|void |
||
456 | */ |
||
457 | public function edit($id = null) |
||
458 | { |
||
459 | $data = []; |
||
460 | if ($this->request->is('post') || $this->request->is('put')) { |
||
461 | $data = $this->request->getData(); |
||
462 | unset($data['id']); |
||
463 | //= make sure only admin can edit these fields |
||
464 | if (!$this->CurrentUser->permission('saito.core.user.edit')) { |
||
465 | // @td DRY: refactor this admin fields together with view |
||
466 | unset($data['username'], $data['user_email'], $data['user_type']); |
||
467 | } |
||
468 | } |
||
469 | $user = $this->_edit($id, $data); |
||
470 | if ($user === true) { |
||
471 | return $this->redirect(['action' => 'view', $id]); |
||
472 | } |
||
473 | |||
474 | $this->set('user', $user); |
||
475 | $this->set( |
||
476 | 'titleForPage', |
||
477 | __('user.edit.t', [$user->get('username')]) |
||
478 | ); |
||
479 | |||
480 | $availableThemes = $this->Themes->getAvailable($this->CurrentUser); |
||
481 | $availableThemes = array_combine($availableThemes, $availableThemes); |
||
482 | $currentTheme = $this->Themes->getThemeForUser($this->CurrentUser); |
||
483 | $this->set(compact('availableThemes', 'currentTheme')); |
||
484 | } |
||
485 | |||
486 | /** |
||
487 | * Handle user edit core. Retrieve user or patch if data is passed. |
||
488 | * |
||
489 | * @param string $userId user-ID |
||
490 | * @param array|null $data datat to update the user |
||
491 | * |
||
492 | * @return true|User true on successful save, patched user otherwise |
||
493 | */ |
||
494 | protected function _edit($userId, array $data = null) |
||
523 | |||
524 | /** |
||
525 | * Lock user. |
||
526 | * |
||
527 | * @return \Cake\Network\Response|void |
||
528 | * @throws BadRequestException |
||
529 | */ |
||
530 | public function lock() |
||
566 | |||
567 | /** |
||
568 | * Unblock user. |
||
569 | * |
||
570 | * @param string $id user-ID |
||
571 | * @return void |
||
572 | */ |
||
573 | public function unlock($id) |
||
591 | |||
592 | /** |
||
593 | * changes user password |
||
594 | * |
||
595 | * @param null $id user-ID |
||
596 | * @return void |
||
597 | * @throws \Saito\Exception\SaitoForbiddenException |
||
598 | * @throws BadRequestException |
||
599 | */ |
||
600 | public function changepassword($id = null) |
||
655 | |||
656 | /** |
||
657 | * Directly set password for user |
||
658 | * |
||
659 | * @param string $id user-ID |
||
660 | * @return Response|null |
||
661 | */ |
||
662 | public function setpassword($id) |
||
695 | |||
696 | /** |
||
697 | * Set slidetab-order. |
||
698 | * |
||
699 | * @return \Cake\Network\Response |
||
700 | * @throws BadRequestException |
||
701 | */ |
||
702 | public function slidetabOrder() |
||
733 | |||
734 | /** |
||
735 | * Shows user's uploads |
||
736 | * |
||
737 | * @return void |
||
738 | */ |
||
739 | public function uploads() |
||
742 | |||
743 | /** |
||
744 | * Set category for user. |
||
745 | * |
||
746 | * @param null $id category-ID |
||
747 | * @return \Cake\Network\Response |
||
748 | * @throws ForbiddenException |
||
749 | */ |
||
750 | public function setcategory($id = null) |
||
764 | |||
765 | /** |
||
766 | * {@inheritdoc} |
||
767 | */ |
||
768 | public function beforeFilter(Event $event) |
||
783 | |||
784 | /** |
||
785 | * Checks if the current user is allowed to edit user $userId |
||
786 | * |
||
787 | * @param ForumsUserInterface $CurrentUser user |
||
788 | * @param int $userId user-ID |
||
789 | * @return bool |
||
790 | */ |
||
791 | protected function _isEditingAllowed(ForumsUserInterface $CurrentUser, $userId) |
||
799 | } |
||
800 |
If you implement
__call
and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.This is often the case, when
__call
is implemented by a parent class and only the child class knows which methods exist: