Total Complexity | 45 |
Total Lines | 395 |
Duplicated Lines | 0 % |
Coverage | 2.19% |
Changes | 6 | ||
Bugs | 0 | Features | 0 |
Complex classes like IssueCloner 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.
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 IssueCloner, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class IssueCloner |
||
19 | { |
||
20 | |||
21 | const LINK_DIRECTION_INWARD = 1; |
||
22 | |||
23 | const LINK_DIRECTION_OUTWARD = 2; |
||
24 | |||
25 | /** |
||
26 | * Jira REST client. |
||
27 | * |
||
28 | * @var JiraApi |
||
29 | */ |
||
30 | protected $jiraApi; |
||
31 | |||
32 | /** |
||
33 | * Specifies custom fields to copy during backporting. |
||
34 | * |
||
35 | * @var array |
||
36 | */ |
||
37 | private $_copyCustomFields = array( |
||
38 | 'Change Log Group', 'Change Log Message', |
||
39 | ); |
||
40 | |||
41 | /** |
||
42 | * Custom fields map. |
||
43 | * |
||
44 | * @var array |
||
45 | */ |
||
46 | private $_customFieldsMap = array(); |
||
47 | |||
48 | /** |
||
49 | * Fields to query during issue search. |
||
50 | * |
||
51 | * @var array |
||
52 | */ |
||
53 | protected $queryFields = array('summary', 'issuelinks'); |
||
54 | |||
55 | /** |
||
56 | * Project of an issue. |
||
57 | * |
||
58 | * @var array |
||
59 | */ |
||
60 | private $_issueProjects = array(); |
||
61 | |||
62 | /** |
||
63 | * IssueCloner constructor. |
||
64 | * |
||
65 | * @param JiraApi $jira_api Jira REST client. |
||
66 | */ |
||
67 | 2 | public function __construct(JiraApi $jira_api) |
|
68 | { |
||
69 | 2 | $this->jiraApi = $jira_api; |
|
70 | |||
71 | 2 | $this->jiraApi->setOptions(0); // Don't expand fields. |
|
72 | } |
||
73 | |||
74 | /** |
||
75 | * Returns issues. |
||
76 | * |
||
77 | * @param string $jql JQL. |
||
78 | * @param string $link_name Link name. |
||
79 | * @param integer $link_direction Link direction. |
||
80 | * @param array $link_project_keys Link project keys. |
||
81 | * |
||
82 | * @return array |
||
83 | */ |
||
84 | public function getIssues($jql, $link_name, $link_direction, array $link_project_keys) |
||
85 | { |
||
86 | $this->_buildCustomFieldsMap(); |
||
87 | |||
88 | $walker = new Walker($this->jiraApi); |
||
89 | $walker->push($jql, implode(',', $this->_getQueryFields())); |
||
90 | |||
91 | $cache = array(); |
||
92 | |||
93 | foreach ( $walker as $issue ) { |
||
94 | foreach ( $link_project_keys as $link_project_key ) { |
||
95 | $linked_issue = $this->_getLinkedIssue($issue, $link_name, $link_direction); |
||
96 | |||
97 | if ( $linked_issue !== null ) { |
||
98 | $this->_addToProjectDetectionQueue($linked_issue); |
||
99 | } |
||
100 | |||
101 | $cache[] = array($issue, $linked_issue, $link_project_key); |
||
102 | } |
||
103 | } |
||
104 | |||
105 | $this->_processProjectDetectionQueue(); |
||
106 | |||
107 | $ret = array(); |
||
108 | |||
109 | foreach ( $cache as $cached_data ) { |
||
110 | list($issue, $linked_issue, $link_project_key) = $cached_data; |
||
111 | |||
112 | if ( $linked_issue === null ) { |
||
113 | $ret[] = $cached_data; |
||
114 | continue; |
||
115 | } |
||
116 | |||
117 | if ( $this->isLinkAccepted($issue, $linked_issue) |
||
118 | && $this->_getIssueProject($linked_issue) === $link_project_key |
||
119 | && !$this->isAlreadyProcessed($issue, $linked_issue) |
||
120 | ) { |
||
121 | $ret[] = $cached_data; |
||
122 | } |
||
123 | } |
||
124 | |||
125 | return $ret; |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Adds an issue to the project detection queue. |
||
130 | * |
||
131 | * @param Issue $issue Issue. |
||
132 | * |
||
133 | * @return void |
||
134 | */ |
||
135 | private function _addToProjectDetectionQueue(Issue $issue) |
||
136 | { |
||
137 | $this->_issueProjects[$issue->getKey()] = null; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Detects projects for queued issues. |
||
142 | * |
||
143 | * @return void |
||
144 | */ |
||
145 | private function _processProjectDetectionQueue() |
||
146 | { |
||
147 | $issues_without_projects = array_keys(array_filter($this->_issueProjects, function ($project_key) { |
||
148 | return $project_key === null; |
||
149 | })); |
||
150 | |||
151 | if ( !$issues_without_projects ) { |
||
1 ignored issue
–
show
|
|||
152 | return; |
||
153 | } |
||
154 | |||
155 | $walker = new Walker($this->jiraApi); |
||
156 | $walker->push( |
||
157 | 'key IN (' . implode(',', $issues_without_projects) . ')', |
||
158 | 'project' |
||
159 | ); |
||
160 | |||
161 | foreach ( $walker as $linked_issue ) { |
||
162 | $linked_issue_project_data = $linked_issue->get('project'); |
||
163 | $this->_issueProjects[$linked_issue->getKey()] = $linked_issue_project_data['key']; |
||
164 | } |
||
165 | } |
||
166 | |||
167 | /** |
||
168 | * Returns an issue project. |
||
169 | * |
||
170 | * @param Issue $issue Issue. |
||
171 | * |
||
172 | * @return string |
||
173 | * @throws \RuntimeException When issue's project isn't available. |
||
174 | */ |
||
175 | private function _getIssueProject(Issue $issue) |
||
176 | { |
||
177 | $issue_key = $issue->getKey(); |
||
178 | |||
179 | if ( array_key_exists($issue_key, $this->_issueProjects) ) { |
||
180 | return $this->_issueProjects[$issue_key]; |
||
181 | } |
||
182 | |||
183 | throw new \RuntimeException('The issue "' . $issue_key . '" project wasn\'t queried.'); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Builds custom field map. |
||
188 | * |
||
189 | * @return void |
||
190 | */ |
||
191 | private function _buildCustomFieldsMap() |
||
192 | { |
||
193 | foreach ( $this->jiraApi->getFields() as $field_key => $field_data ) { |
||
194 | if ( substr($field_key, 0, 12) === 'customfield_' ) { |
||
195 | $this->_customFieldsMap[$field_data['name']] = $field_key; |
||
196 | } |
||
197 | } |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Returns query fields. |
||
202 | * |
||
203 | * @return array |
||
204 | */ |
||
205 | private function _getQueryFields() |
||
206 | { |
||
207 | $ret = $this->queryFields; |
||
208 | |||
209 | foreach ( $this->_copyCustomFields as $custom_field ) { |
||
210 | if ( isset($this->_customFieldsMap[$custom_field]) ) { |
||
211 | $ret[] = $this->_customFieldsMap[$custom_field]; |
||
212 | } |
||
213 | } |
||
214 | |||
215 | return $ret; |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * Returns issue, which backports given issue (project not matched yet). |
||
220 | * |
||
221 | * @param Issue $issue Issue. |
||
222 | * @param string $link_name Link name. |
||
223 | * @param integer $link_direction Link direction. |
||
224 | * |
||
225 | * @return Issue|null |
||
226 | * @throws \InvalidArgumentException When link direction isn't valid. |
||
227 | */ |
||
228 | private function _getLinkedIssue(Issue $issue, $link_name, $link_direction) |
||
229 | { |
||
230 | foreach ( $issue->get('issuelinks') as $issue_link ) { |
||
231 | if ( $issue_link['type']['name'] !== $link_name ) { |
||
232 | continue; |
||
233 | } |
||
234 | |||
235 | if ( $link_direction === self::LINK_DIRECTION_INWARD ) { |
||
236 | $check_key = 'inwardIssue'; |
||
237 | } |
||
238 | elseif ( $link_direction === self::LINK_DIRECTION_OUTWARD ) { |
||
239 | $check_key = 'outwardIssue'; |
||
240 | } |
||
241 | else { |
||
242 | throw new \InvalidArgumentException('The "' . $link_direction . '" link direction isn\'t valid.'); |
||
243 | } |
||
244 | |||
245 | if ( array_key_exists($check_key, $issue_link) ) { |
||
246 | return new Issue($issue_link[$check_key]); |
||
247 | } |
||
248 | } |
||
249 | |||
250 | return null; |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * Creates backports issues. |
||
255 | * |
||
256 | * @param Issue $issue Issue. |
||
257 | * @param string $project_key Project key. |
||
258 | * @param string $link_name Link name. |
||
259 | * @param integer $link_direction Link direction. |
||
260 | * @param array $component_ids Component IDs. |
||
261 | * |
||
262 | * @return string |
||
263 | * @throws \RuntimeException When failed to create an issue. |
||
264 | * @throws \InvalidArgumentException When link direction isn't valid. |
||
265 | */ |
||
266 | public function createLinkedIssue(Issue $issue, $project_key, $link_name, $link_direction, array $component_ids) |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Determines if link was already processed. |
||
332 | * |
||
333 | * @param Issue $issue Issue. |
||
334 | * @param Issue $linked_issue Linked issue. |
||
335 | * |
||
336 | * @return boolean |
||
337 | */ |
||
338 | protected function isAlreadyProcessed(Issue $issue, Issue $linked_issue) |
||
339 | { |
||
340 | return false; |
||
341 | } |
||
342 | |||
343 | /** |
||
344 | * Determines if link is accepted. |
||
345 | * |
||
346 | * @param Issue $issue Issue. |
||
347 | * @param Issue $linked_issue Linked issue. |
||
348 | * |
||
349 | * @return boolean |
||
350 | */ |
||
351 | protected function isLinkAccepted(Issue $issue, Issue $linked_issue) |
||
352 | { |
||
353 | return true; |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * Returns ID of "Changelog Entry" issue type. |
||
358 | * |
||
359 | * @return integer |
||
360 | * @throws \LogicException When "Changelog Entry" issue type wasn't found. |
||
361 | */ |
||
362 | protected function getChangelogEntryIssueTypeId() |
||
363 | { |
||
364 | static $issue_type_id; |
||
365 | |||
366 | if ( !isset($issue_type_id) ) { |
||
367 | foreach ( $this->jiraApi->getIssueTypes() as $issue_type ) { |
||
368 | if ( $issue_type->getName() === 'Changelog Entry' ) { |
||
369 | $issue_type_id = $issue_type->getId(); |
||
370 | break; |
||
371 | } |
||
372 | } |
||
373 | |||
374 | if ( !isset($issue_type_id) ) { |
||
375 | throw new \LogicException('The "Changelog Entry" issue type not found.'); |
||
376 | } |
||
377 | } |
||
378 | |||
379 | return $issue_type_id; |
||
380 | } |
||
381 | |||
382 | /** |
||
383 | * Returns custom field value. |
||
384 | * |
||
385 | * @param Issue $issue Issue. |
||
386 | * @param string $custom_field_id Custom field ID. |
||
387 | * |
||
388 | * @return mixed |
||
389 | */ |
||
390 | protected function getIssueCustomField(Issue $issue, $custom_field_id) |
||
391 | { |
||
392 | $custom_field_data = $issue->get($custom_field_id); |
||
393 | |||
394 | if ( is_array($custom_field_data) ) { |
||
395 | return array('value' => $custom_field_data['value']); |
||
396 | } |
||
397 | |||
398 | return $custom_field_data; |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * Returns issue status name. |
||
403 | * |
||
404 | * @param Issue $issue Issue. |
||
405 | * |
||
406 | * @return string |
||
407 | */ |
||
408 | public function getIssueStatusName(Issue $issue) |
||
413 | } |
||
414 | |||
415 | } |
||
416 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.