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 SendMessages 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 SendMessages, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
28 | class SendMessages extends SendMessagesAbstract |
||
29 | { |
||
30 | protected $template = 'update_issue'; |
||
31 | |||
32 | /** |
||
33 | * Collection of tags. |
||
34 | * |
||
35 | * @var Collection |
||
36 | */ |
||
37 | protected $tags; |
||
38 | |||
39 | /** |
||
40 | * Returns an instance of Issue. |
||
41 | * |
||
42 | * @return bool |
||
43 | */ |
||
44 | 3 | protected function getIssue() |
|
52 | |||
53 | /** |
||
54 | * Returns an instance of Project. |
||
55 | * |
||
56 | * @return Project |
||
57 | */ |
||
58 | 3 | protected function getProject() |
|
62 | |||
63 | /** |
||
64 | * Returns the project id. |
||
65 | * |
||
66 | * @return int |
||
67 | */ |
||
68 | 3 | protected function getProjectId() |
|
72 | |||
73 | /** |
||
74 | * Returns message data belongs to adding an issue. |
||
75 | * |
||
76 | * @param Queue $queue |
||
77 | * |
||
78 | * @return array |
||
79 | */ |
||
80 | 3 | protected function getMessageDataForAddIssue(Queue $queue) |
|
81 | { |
||
82 | 3 | $messageData = ['changes' => []]; |
|
83 | 3 | $messageData['changeByHeading'] = $queue->changeBy->fullname . ' created a new issue'; |
|
84 | 3 | if ($this->issue->assigned) { |
|
85 | 1 | $messageData['changes']['assignee'] = $this->issue->assigned->fullname; |
|
86 | } |
||
87 | |||
88 | 3 | $tags = $this->issue->tags()->with('parent')->get(); |
|
89 | 3 | foreach ($tags as $tag) { |
|
90 | 2 | $tagArray = $tag->toShortArray(); |
|
91 | 2 | $tagArray['now'] = $tagArray['name']; |
|
92 | 2 | $messageData['changes'][$tag->parent->name] = $tagArray; |
|
93 | } |
||
94 | |||
95 | 3 | if ($this->issue->time_quote && !$this->issue->isQuoteLocked()) { |
|
96 | $messageData['changes']['time_quote'] = \Html::duration($this->issue->time_quote); |
||
97 | } |
||
98 | |||
99 | 3 | return $messageData; |
|
100 | } |
||
101 | |||
102 | /** |
||
103 | * Returns message data belongs to updating an issue. |
||
104 | * |
||
105 | * @param Queue $queue |
||
106 | * |
||
107 | * @return array |
||
108 | */ |
||
109 | 1 | protected function getMessageDataForUpdateIssue(Queue $queue) |
|
110 | { |
||
111 | 1 | $messageData = []; |
|
112 | 1 | $messageData['changeByHeading'] = $queue->changeBy->fullname . ' updated an issue'; |
|
113 | 1 | $whiteListFields = ['change_by', 'title', 'body', 'assignee', 'status', 'type', 'resolution', 'time_quote']; |
|
114 | |||
115 | 1 | foreach ($queue->payload['dirty'] as $field => $value) { |
|
116 | // Skip fields that not part of the white list or quote is locked |
||
117 | 1 | if (!in_array($field, $whiteListFields) || ($field == 'time_quote' && $this->issue->isQuoteLocked())) { |
|
118 | continue; |
||
119 | } |
||
120 | |||
121 | // Format quote to readable time |
||
122 | 1 | $value = $field === 'time_quote' ? \Html::duration($value) : $value; |
|
123 | 1 | $value = $field === 'body' ? \Html::format($value) : $value; |
|
124 | 1 | $messageData['changes'][$field] = [ |
|
125 | 1 | 'now' => $value, |
|
126 | 1 | 'was' => $queue->getDataFromPayload('origin.' . $field), |
|
127 | ]; |
||
128 | } |
||
129 | |||
130 | 1 | return $messageData; |
|
131 | } |
||
132 | |||
133 | /** |
||
134 | * Returns message data belongs to reopening an issue. |
||
135 | * |
||
136 | * @param Queue $queue |
||
137 | * |
||
138 | * @return array |
||
139 | */ |
||
140 | 1 | protected function getMessageDataForReopenIssue(Queue $queue) |
|
141 | { |
||
142 | 1 | $messageData = []; |
|
143 | 1 | $messageData['changeByHeading'] = $queue->changeBy->fullname . ' reopened an issue'; |
|
144 | 1 | $statusNow = $this->issue |
|
145 | 1 | ->tags()->with('parent')->get()->where('parent.name', Tag::GROUP_STATUS)->last(); |
|
146 | 1 | $messageData['changes']['status'] = [ |
|
147 | 1 | 'was' => trans('tinyissue.closed'), |
|
148 | 1 | 'now' => ($statusNow ? $statusNow->fullname : ''), |
|
149 | 1 | 'id' => ($statusNow ? $statusNow->id : ''), |
|
150 | ]; |
||
151 | |||
152 | 1 | return $messageData; |
|
153 | } |
||
154 | |||
155 | /** |
||
156 | * Returns message data belongs to closing an issue. |
||
157 | * |
||
158 | * @param Queue $queue |
||
159 | * |
||
160 | * @return array |
||
161 | */ |
||
162 | 2 | protected function getMessageDataForCloseIssue(Queue $queue) |
|
163 | { |
||
164 | 2 | $messageData = []; |
|
165 | 2 | $messageData['changeByHeading'] = $queue->changeBy->fullname . ' closed an issue'; |
|
166 | 2 | $statusWas = $this->issue |
|
167 | 2 | ->tags()->with('parent')->get()->where('parent.name', Tag::GROUP_STATUS)->last(); |
|
168 | 2 | $messageData['changes']['status'] = [ |
|
169 | 2 | 'was' => ($statusWas ? $statusWas->fullname : ''), |
|
170 | 2 | 'now' => trans('tinyissue.closed'), |
|
171 | ]; |
||
172 | |||
173 | 2 | return $messageData; |
|
174 | } |
||
175 | |||
176 | /** |
||
177 | * Returns message data belongs to assigning an issue to a user. |
||
178 | * |
||
179 | * @param Queue $queue |
||
180 | * @param array $extraData |
||
181 | * |
||
182 | * @return array |
||
183 | */ |
||
184 | 1 | protected function getMessageDataForAssignIssue(Queue $queue, array $extraData) |
|
185 | { |
||
186 | 1 | $messageData = []; |
|
187 | 1 | if (!array_key_exists('now', $extraData)) { |
|
188 | 1 | $assignTo = $this->getUserById($queue->getDataFromPayload('dirty.assigned_to')); |
|
189 | 1 | $extraData = ['now' => $assignTo->fullname]; |
|
190 | } |
||
191 | |||
192 | 1 | $messageData['changes']['assignee'] = $extraData; |
|
193 | 1 | $messageData['changeByHeading'] = $queue->changeBy->fullname . ' assigned an issue to ' . $extraData['now']; |
|
194 | |||
195 | 1 | return $messageData; |
|
196 | } |
||
197 | |||
198 | /** |
||
199 | * Returns message data belongs to changing an issue tags. |
||
200 | * |
||
201 | * @param Queue $queue |
||
202 | * |
||
203 | * @return array |
||
204 | */ |
||
205 | 3 | protected function getMessageDataForChangeTagIssue(Queue $queue) |
|
206 | { |
||
207 | 3 | $messageData = []; |
|
208 | 3 | $messageData['changeByHeading'] = $queue->changeBy->fullname . ' changed an issue tag'; |
|
209 | |||
210 | 3 | foreach ($queue->payload['added'] as $tag) { |
|
211 | 3 | $group = strtolower($tag['group']); |
|
212 | 3 | $messageData['changes'][$group] = [ |
|
213 | 3 | 'now' => $tag['name'], |
|
214 | 3 | 'id' => $tag['id'], |
|
215 | 3 | 'message_limit' => $tag['message_limit'], |
|
216 | ]; |
||
217 | } |
||
218 | |||
219 | 3 | foreach ($queue->payload['removed'] as $tag) { |
|
220 | 2 | $group = strtolower($tag['group']); |
|
221 | 2 | $messageData['changes'][$group]['was'] = $tag['name']; |
|
222 | } |
||
223 | |||
224 | 3 | return $messageData; |
|
225 | } |
||
226 | |||
227 | /** |
||
228 | * Check that the issue is load. |
||
229 | * |
||
230 | * @return bool |
||
231 | */ |
||
232 | 3 | protected function validateData() |
|
241 | |||
242 | /** |
||
243 | * Populate assigned relation in the current issue. |
||
244 | * |
||
245 | * @return void |
||
246 | */ |
||
247 | 3 | View Code Duplication | protected function populateData() |
256 | |||
257 | /** |
||
258 | * Check if the latest message is about closing or reopening an issue. |
||
259 | * |
||
260 | * @return bool |
||
261 | */ |
||
262 | 3 | public function isStatusMessage() |
|
267 | |||
268 | /** |
||
269 | * Process assign to user message. Send direct message to new and previous users and full subscribers. |
||
270 | * |
||
271 | * @return void |
||
272 | */ |
||
273 | 3 | protected function processDirectMessages() |
|
274 | { |
||
275 | // Stop if issue is closed |
||
276 | 3 | if (!$this->getIssue()->isOpen()) { |
|
277 | 2 | return; |
|
278 | } |
||
279 | |||
280 | // Fetch all of the assign issue changes |
||
281 | 3 | $assignMessages = $this->allMessages->where('event', Queue::ASSIGN_ISSUE); |
|
282 | |||
283 | // Skip if no changes |
||
284 | 3 | if ($assignMessages->isEmpty()) { |
|
285 | 3 | return; |
|
286 | } |
||
287 | |||
288 | // Fetch the latest assignee |
||
289 | /** @var Queue $assignMessage */ |
||
290 | 1 | $assignMessage = $assignMessages->first(); |
|
291 | |||
292 | // Fetch the user details of the new assignee & previous assignee if this isn't new issue |
||
293 | 1 | $assigns = []; |
|
294 | 1 | $assigns['new'] = (int) $assignMessage->getDataFromPayload('dirty.assigned_to'); |
|
295 | 1 | if (!$this->addMessage) { |
|
296 | 1 | $previousAssignMessage = $assignMessages->last(); |
|
297 | 1 | $assigns['old'] = (int) $previousAssignMessage->getDataFromPayload('origin.assigned_to'); |
|
298 | } |
||
299 | |||
300 | // Fetch users objects for old and new assignee |
||
301 | /** @var \Illuminate\Database\Eloquent\Collection $assignObjects */ |
||
302 | 1 | $assignObjects = (new User())->whereIn('id', $assigns)->get(); |
|
303 | |||
304 | // If for what ever reason the user does not exists, skip this change |
||
305 | // or if there is only one user and is not matching the new assignee. |
||
306 | // then skip this message |
||
307 | 1 | if ($assignObjects->count() === 0 |
|
308 | 1 | || ($assignObjects->count() === 1 && (int) $assignObjects->first()->id !== $assigns['new']) |
|
309 | ) { |
||
310 | return; |
||
311 | } |
||
312 | |||
313 | // Get the object of the new assignee |
||
314 | 1 | $assignTo = $assignObjects->where('id', $assigns['new'], false)->first(); |
|
315 | 1 | $users = collect([$this->createProjectUserObject($assignTo)]); |
|
316 | |||
317 | // Exclude the user from any other message for this issue |
||
318 | 1 | $this->addToExcludeUsers($assignTo); |
|
319 | |||
320 | // Data about new and previous assignee |
||
321 | $extraMessageData = [ |
||
322 | 1 | 'now' => $assignTo->fullname, |
|
323 | ]; |
||
324 | |||
325 | // Make sure that the previous assignee was not the same as the new user |
||
326 | 1 | if (array_key_exists('old', $assigns) && $assigns['old'] > 0 && $assigns['new'] !== $assigns['old']) { |
|
327 | $previousAssign = $assignObjects->where('id', $assigns['old'], false)->first(); |
||
328 | if ($previousAssign) { |
||
329 | $extraMessageData['was'] = $previousAssign->fullname; |
||
330 | $users->push($this->createProjectUserObject($previousAssign)); |
||
331 | } |
||
332 | } |
||
333 | |||
334 | // Get message data needed for the message & send |
||
335 | 1 | $messageData = $this->getMessageData($assignMessage, $extraMessageData); |
|
336 | 1 | $this->sendMessages($users, $messageData); |
|
337 | 1 | } |
|
338 | |||
339 | /** |
||
340 | * Create user project object for a user. |
||
341 | * |
||
342 | * @param User $user |
||
343 | * |
||
344 | * @return Project\User |
||
345 | */ |
||
346 | 1 | protected function createProjectUserObject(User $user) |
|
357 | |||
358 | /** |
||
359 | * Get collection of tags or one by ID. |
||
360 | * |
||
361 | * @param null|int $tagId |
||
362 | * |
||
363 | * @return \Illuminate\Support\Collection|Tag |
||
364 | */ |
||
365 | 3 | protected function getTags($tagId = null) |
|
384 | |||
385 | /** |
||
386 | * Whether or not the user wants to receive the message. |
||
387 | * |
||
388 | * @param Project\User $user |
||
389 | * @param array $data |
||
390 | * |
||
391 | * @return bool |
||
392 | */ |
||
393 | 3 | protected function wantToReceiveMessage(Project\User $user, array $data) |
|
429 | } |
||
430 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.