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 BlogController 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 BlogController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
10 | class BlogController extends AppController |
||
11 | { |
||
12 | |||
13 | /** |
||
14 | * Initialization hook method. |
||
15 | * |
||
16 | * @return void |
||
17 | */ |
||
18 | public function initialize() |
||
19 | { |
||
20 | parent::initialize(); |
||
21 | |||
22 | $this->loadComponent('RequestHandler'); |
||
23 | } |
||
24 | |||
25 | /** |
||
26 | * BeforeFilter handle. |
||
27 | * |
||
28 | * @param Event $event The beforeFilter event that was fired. |
||
29 | * |
||
30 | * @return void |
||
31 | */ |
||
32 | public function beforeFilter(Event $event) |
||
33 | { |
||
34 | parent::beforeFilter($event); |
||
35 | |||
36 | $this->Auth->allow(['index', 'category', 'article', 'go', 'archive', 'search']); |
||
37 | } |
||
38 | |||
39 | /** |
||
40 | * Display all Articles. |
||
41 | * |
||
42 | * @return void |
||
43 | */ |
||
44 | View Code Duplication | public function index() |
|
|
|||
45 | { |
||
46 | $this->loadModel('BlogArticles'); |
||
47 | $this->paginate = [ |
||
48 | 'maxLimit' => Configure::read('Blog.article_per_page') |
||
49 | ]; |
||
50 | |||
51 | $articles = $this->BlogArticles |
||
52 | ->find() |
||
53 | ->contain([ |
||
54 | 'BlogCategories', |
||
55 | 'Users' => function ($q) { |
||
56 | return $q->find('short'); |
||
57 | } |
||
58 | ]) |
||
59 | ->order([ |
||
60 | 'BlogArticles.created' => 'desc' |
||
61 | ]) |
||
62 | ->where([ |
||
63 | 'BlogArticles.is_display' => 1 |
||
64 | ]); |
||
65 | |||
66 | $articles = $this->paginate($articles); |
||
67 | |||
68 | $this->set(compact('articles')); |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * Display a specific category with all its articles. |
||
73 | * |
||
74 | * @return \Cake\Network\Response|void |
||
75 | */ |
||
76 | public function category() |
||
77 | { |
||
78 | $this->loadModel('BlogCategories'); |
||
79 | |||
80 | $category = $this->BlogCategories |
||
81 | ->find() |
||
82 | ->where([ |
||
83 | 'BlogCategories.id' => $this->request->id |
||
84 | ]) |
||
85 | ->contain([ |
||
86 | 'BlogArticles' |
||
87 | ]) |
||
88 | ->first(); |
||
89 | |||
90 | //Check if the category is found. |
||
91 | if (empty($category)) { |
||
92 | $this->Flash->error(__('This category doesn\'t exist or has been deleted.')); |
||
93 | |||
94 | return $this->redirect(['action' => 'index']); |
||
95 | } |
||
96 | |||
97 | //Paginate all Articles. |
||
98 | $this->loadModel('BlogArticles'); |
||
99 | $this->paginate = [ |
||
100 | 'maxLimit' => Configure::read('Blog.article_per_page') |
||
101 | ]; |
||
102 | |||
103 | $articles = $this->BlogArticles |
||
104 | ->find() |
||
105 | ->contain([ |
||
106 | 'Users' => function ($q) { |
||
107 | return $q->find('short'); |
||
108 | } |
||
109 | ]) |
||
110 | ->where([ |
||
111 | 'BlogArticles.category_id' => $category->id, |
||
112 | 'BlogArticles.is_display' => 1 |
||
113 | ]) |
||
114 | ->order([ |
||
115 | 'BlogArticles.created' => 'desc' |
||
116 | ]); |
||
117 | |||
118 | $articles = $this->paginate($articles); |
||
119 | |||
120 | $this->set(compact('category', 'articles')); |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Display a specific article. |
||
125 | * |
||
126 | * @return \Cake\Network\Response|void |
||
127 | */ |
||
128 | public function article() |
||
129 | { |
||
130 | $this->loadModel('BlogArticles'); |
||
131 | |||
132 | $article = $this->BlogArticles |
||
133 | ->find() |
||
134 | ->where([ |
||
135 | 'BlogArticles.id' => $this->request->id, |
||
136 | 'BlogArticles.is_display' => 1 |
||
137 | ]) |
||
138 | ->contain([ |
||
139 | 'BlogCategories', |
||
140 | 'BlogAttachments', |
||
141 | 'Users' => function ($q) { |
||
142 | return $q->find('full'); |
||
143 | } |
||
144 | ]) |
||
145 | ->first(); |
||
146 | |||
147 | //Check if the article is found. |
||
148 | if (is_null($article)) { |
||
149 | $this->Flash->error(__('This article doesn\'t exist or has been deleted.')); |
||
150 | |||
151 | return $this->redirect(['action' => 'index']); |
||
152 | } |
||
153 | |||
154 | $this->loadModel('BlogArticlesComments'); |
||
155 | |||
156 | //A comment has been posted. |
||
157 | if ($this->request->is('post')) { |
||
158 | //Check if the user is connected. |
||
159 | if (!$this->Auth->user()) { |
||
160 | return $this->Flash->error(__('You must be connected to post a comment.')); |
||
161 | } |
||
162 | |||
163 | $this->request->data['article_id'] = $article->id; |
||
164 | $this->request->data['user_id'] = $this->Auth->user('id'); |
||
165 | |||
166 | $newComment = $this->BlogArticlesComments->newEntity($this->request->data, ['validate' => 'create']); |
||
167 | |||
168 | //Attach Event. |
||
169 | $this->BlogArticlesComments->eventManager()->attach(new Badges($this)); |
||
170 | |||
171 | if ($insertComment = $this->BlogArticlesComments->save($newComment)) { |
||
172 | $this->Flash->success(__('Your comment has been posted successfully !')); |
||
173 | //Redirect the user to the last page of the article. |
||
174 | $this->redirect([ |
||
175 | 'action' => 'go', |
||
176 | $insertComment->id |
||
177 | ]); |
||
178 | } |
||
179 | } |
||
180 | |||
181 | //Paginate all comments related to the article. |
||
182 | $this->paginate = [ |
||
183 | 'maxLimit' => Configure::read('Blog.comment_per_page') |
||
184 | ]; |
||
185 | |||
186 | $comments = $this->BlogArticlesComments |
||
187 | ->find() |
||
188 | ->where([ |
||
189 | 'BlogArticlesComments.article_id' => $article->id |
||
190 | ]) |
||
191 | ->contain([ |
||
192 | 'Users' => function ($q) { |
||
193 | return $q->find('medium'); |
||
194 | } |
||
195 | ]) |
||
196 | ->order([ |
||
197 | 'BlogArticlesComments.created' => 'asc' |
||
198 | ]); |
||
199 | |||
200 | $comments = $this->paginate($comments); |
||
201 | |||
202 | //Select the like for the current auth user. |
||
203 | $this->loadModel('BlogArticlesLikes'); |
||
204 | $like = $this->BlogArticlesLikes |
||
205 | ->find() |
||
206 | ->where([ |
||
207 | 'user_id' => ($this->Auth->user()) ? $this->Auth->user('id') : null, |
||
208 | 'article_id' => $article->id |
||
209 | ]) |
||
210 | ->first(); |
||
211 | |||
212 | //Build the newEntity for the comment form. |
||
213 | $formComments = $this->BlogArticlesComments->newEntity(); |
||
214 | |||
215 | //Search related articles |
||
216 | $keywords = preg_split("/([\s,\W])+/", $article->title); |
||
217 | |||
218 | $articles = $this->BlogArticles |
||
219 | ->find() |
||
220 | ->contain([ |
||
221 | 'BlogCategories', |
||
222 | ]) |
||
223 | ->where([ |
||
224 | 'BlogArticles.is_display' => 1, |
||
225 | 'BlogArticles.id !=' => $article->id |
||
226 | ]) |
||
227 | ->andWhere([ |
||
228 | 'BlogArticles.title RLIKE' => rtrim(implode('|', $keywords), '|') |
||
229 | ]); |
||
230 | |||
231 | //Current user. |
||
232 | $this->loadModel('Users'); |
||
233 | $currentUser = $this->Users |
||
234 | ->find() |
||
235 | ->contain([ |
||
236 | 'Groups' => function ($q) { |
||
237 | return $q->select(['id', 'is_staff']); |
||
238 | } |
||
239 | ]) |
||
240 | ->where([ |
||
241 | 'Users.id' => $this->Auth->user('id') |
||
242 | ]) |
||
243 | ->select(['id', 'group_id']) |
||
244 | ->first(); |
||
245 | |||
246 | $this->set(compact('article', 'formComments', 'comments', 'like', 'articles', 'currentUser')); |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * Quote a message. |
||
251 | * |
||
252 | * @param int $articleId Id of the article where is the message to quote. |
||
253 | * @param int $commentId Id of the message to quote. |
||
254 | * |
||
255 | * @throws \Cake\Network\Exception\NotFoundException |
||
256 | * |
||
257 | * @return mixed |
||
258 | */ |
||
259 | public function quote($articleId = null, $commentId = null) |
||
260 | { |
||
261 | if (!$this->request->is('ajax')) { |
||
262 | throw new NotFoundException(); |
||
263 | } |
||
264 | |||
265 | $this->loadModel('BlogArticlesComments'); |
||
266 | |||
267 | $comment = $this->BlogArticlesComments |
||
268 | ->find() |
||
269 | ->where([ |
||
270 | 'BlogArticlesComments.article_id' => $articleId, |
||
271 | 'BlogArticlesComments.id' => $commentId |
||
272 | ]) |
||
273 | ->contain([ |
||
274 | 'Users' => function ($q) { |
||
275 | return $q->find('short'); |
||
276 | } |
||
277 | ]) |
||
278 | ->first(); |
||
279 | |||
280 | $json = []; |
||
281 | |||
282 | if (!is_null($comment)) { |
||
283 | $comment->toArray(); |
||
284 | |||
285 | $url = Router::url(['action' => 'go', $comment->id]); |
||
286 | $text = __("has said :"); |
||
287 | |||
288 | //Build the quote. |
||
289 | $json['comment'] = <<<EOT |
||
290 | <div> |
||
291 | <div> |
||
292 | <a href="{$url}"> |
||
293 | <strong>{$comment->user->full_name} {$text}</strong> |
||
294 | </a> |
||
295 | </div> |
||
296 | <blockquote> |
||
297 | $comment->content |
||
298 | </blockquote> |
||
299 | </div><p> </p><p> </p> |
||
300 | EOT; |
||
301 | |||
302 | $json['error'] = false; |
||
303 | |||
304 | $this->set(compact('json')); |
||
305 | } else { |
||
306 | $json['comment'] = __("This comment doesn't exist."); |
||
307 | $json['error'] = true; |
||
308 | |||
309 | $this->set(compact('json')); |
||
310 | } |
||
311 | |||
312 | //Send response in JSON. |
||
313 | $this->set('_serialize', 'json'); |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * Redirect an user to an article, page and comment. |
||
318 | * |
||
319 | * @param int $commentId Id of the comment. |
||
320 | * |
||
321 | * @return \Cake\Network\Response |
||
322 | */ |
||
323 | View Code Duplication | public function go($commentId = null) |
|
324 | { |
||
325 | $this->loadModel('BlogArticlesComments'); |
||
326 | |||
327 | $comment = $this->BlogArticlesComments |
||
328 | ->find() |
||
329 | ->contain([ |
||
330 | 'BlogArticles' |
||
331 | ]) |
||
332 | ->where([ |
||
333 | 'BlogArticlesComments.id' => $commentId |
||
334 | ]) |
||
335 | ->first(); |
||
336 | |||
337 | if (is_null($comment)) { |
||
338 | $this->Flash->error(__("This comment doesn't exist or has been deleted.")); |
||
339 | |||
340 | return $this->redirect(['action' => 'index']); |
||
341 | } |
||
342 | |||
343 | $comment->toArray(); |
||
344 | |||
345 | //Count the number of message before this message. |
||
346 | $messagesBefore = $this->BlogArticlesComments |
||
347 | ->find() |
||
348 | ->where([ |
||
349 | 'BlogArticlesComments.article_id' => $comment->article_id, |
||
350 | 'BlogArticlesComments.created <' => $comment->created |
||
351 | ]) |
||
352 | ->count(); |
||
353 | |||
354 | //Get the number of messages per page. |
||
355 | $messagesPerPage = Configure::read('Blog.comment_per_page'); |
||
356 | |||
357 | //Calculate the page. |
||
358 | $page = floor($messagesBefore / $messagesPerPage) + 1; |
||
359 | |||
360 | $page = ($page > 1) ? $page : 1; |
||
361 | |||
362 | //Redirect the user. |
||
363 | return $this->redirect([ |
||
364 | '_name' => 'blog-article', |
||
365 | 'slug' => $comment->blog_article->title, |
||
366 | 'id' => $comment->blog_article->id, |
||
367 | '?' => ['page' => $page], |
||
368 | '#' => 'comment-' . $commentId |
||
369 | ]); |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * Get all articles by a date formatted to "m-Y". |
||
374 | * |
||
375 | * @param string $date The date of the archive. |
||
376 | * |
||
377 | * @return void |
||
378 | */ |
||
379 | public function archive($date = null) |
||
407 | |||
408 | /** |
||
409 | * Search articles. |
||
410 | * |
||
411 | * @return void |
||
412 | */ |
||
413 | public function search() |
||
456 | |||
457 | /** |
||
458 | * Like an article. |
||
459 | * |
||
460 | * @param int $articleId Id of the article to like. |
||
461 | * |
||
462 | * @throws \Cake\Network\Exception\NotFoundException When it's not an AJAX request. |
||
463 | * |
||
464 | * @return void |
||
465 | */ |
||
466 | public function articleLike($articleId = null) |
||
538 | |||
539 | /** |
||
540 | * Unlike an article. |
||
541 | * |
||
542 | * @param int|null $articleId Id of the article to like. |
||
543 | * |
||
544 | * @throws \Cake\Network\Exception\NotFoundException When it's not an AJAX request. |
||
545 | * |
||
546 | * @return void |
||
547 | */ |
||
548 | public function articleUnlike($articleId = null) |
||
595 | |||
596 | /** |
||
597 | * Delete a comment. |
||
598 | * |
||
599 | * @param int $id Id of the comment to delete. |
||
600 | * |
||
601 | * @return \Cake\Network\Response |
||
602 | */ |
||
603 | public function deleteComment($id = null) |
||
650 | |||
651 | /** |
||
652 | * Get the form to edit a comment. |
||
653 | * |
||
654 | * @throws \Cake\Network\Exception\NotFoundException When it's not an AJAX request. |
||
655 | * |
||
656 | * @return void |
||
657 | */ |
||
658 | public function getEditComment() |
||
712 | |||
713 | /** |
||
714 | * Edit a comment. |
||
715 | * |
||
716 | * @param int $id Id of the comment. |
||
717 | * |
||
718 | * @return \Cake\Network\Response |
||
719 | */ |
||
720 | public function editComment($id = null) |
||
768 | } |
||
769 |
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.