Total Complexity | 52 |
Total Lines | 370 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like ConfigureRolesHandler 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 ConfigureRolesHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
44 | class ConfigureRolesHandler implements FileConfigurationInterface |
||
|
|||
45 | { |
||
46 | /** @var AttachmentHelperInterface */ |
||
47 | private $attachments; |
||
48 | /** @var array */ |
||
49 | private $examples; |
||
50 | /** @var ImportJob */ |
||
51 | private $importJob; |
||
52 | /** @var ImportJobRepositoryInterface */ |
||
53 | private $repository; |
||
54 | /** @var int */ |
||
55 | private $totalColumns; |
||
56 | |||
57 | /** |
||
58 | * Verifies that the configuration of the job is actually complete, and valid. |
||
59 | * |
||
60 | * @param array $config |
||
61 | * |
||
62 | * @return MessageBag |
||
63 | * |
||
64 | */ |
||
65 | public function configurationComplete(array $config): MessageBag |
||
66 | { |
||
67 | /** @var array $roles */ |
||
68 | $roles = $config['column-roles']; |
||
69 | $assigned = 0; |
||
70 | |||
71 | // check if data actually contains amount column (foreign amount does not count) |
||
72 | $hasAmount = false; |
||
73 | $hasForeignAmount = false; |
||
74 | $hasForeignCode = false; |
||
75 | foreach ($roles as $role) { |
||
76 | if ('_ignore' !== $role) { |
||
77 | ++$assigned; |
||
78 | } |
||
79 | if (in_array($role, ['amount', 'amount_credit', 'amount_debit', 'amount_negated'])) { |
||
80 | $hasAmount = true; |
||
81 | } |
||
82 | if ('foreign-currency-code' === $role) { |
||
83 | $hasForeignCode = true; |
||
84 | } |
||
85 | if ('amount_foreign' === $role) { |
||
86 | $hasForeignAmount = true; |
||
87 | } |
||
88 | } |
||
89 | |||
90 | // all assigned and correct foreign info |
||
91 | if ($assigned > 0 && $hasAmount && ($hasForeignCode === $hasForeignAmount)) { |
||
92 | return new MessageBag; |
||
93 | } |
||
94 | if (0 === $assigned || !$hasAmount) { |
||
95 | $message = (string)trans('import.job_config_roles_rwarning'); |
||
96 | $messages = new MessageBag(); |
||
97 | $messages->add('error', $message); |
||
98 | |||
99 | return $messages; |
||
100 | } |
||
101 | |||
102 | // warn if has foreign amount but no currency code: |
||
103 | if ($hasForeignAmount && !$hasForeignCode) { |
||
104 | $message = (string)trans('import.job_config_roles_fa_warning'); |
||
105 | $messages = new MessageBag(); |
||
106 | $messages->add('error', $message); |
||
107 | |||
108 | return $messages; |
||
109 | } |
||
110 | |||
111 | |||
112 | return new MessageBag; // @codeCoverageIgnore |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Store data associated with current stage. |
||
117 | * |
||
118 | * @param array $data |
||
119 | * |
||
120 | * @return MessageBag |
||
121 | */ |
||
122 | public function configureJob(array $data): MessageBag |
||
123 | { |
||
124 | $config = $this->importJob->configuration; |
||
125 | $count = $config['column-count']; |
||
126 | for ($i = 0; $i < $count; ++$i) { |
||
127 | $role = $data['role'][$i] ?? '_ignore'; |
||
128 | $mapping = (isset($data['map'][$i]) && '1' === $data['map'][$i]); |
||
129 | $config['column-roles'][$i] = $role; |
||
130 | $config['column-do-mapping'][$i] = $mapping; |
||
131 | Log::debug(sprintf('Column %d has been given role %s (mapping: %s)', $i, $role, var_export($mapping, true))); |
||
132 | } |
||
133 | $config = $this->ignoreUnmappableColumns($config); |
||
134 | $messages = $this->configurationComplete($config); |
||
135 | |||
136 | if (0 === $messages->count()) { |
||
137 | $this->repository->setStage($this->importJob, 'ready_to_run'); |
||
138 | if ($this->isMappingNecessary($config)) { |
||
139 | $this->repository->setStage($this->importJob, 'map'); |
||
140 | } |
||
141 | $this->repository->setConfiguration($this->importJob, $config); |
||
142 | } |
||
143 | |||
144 | return $messages; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Extracts example data from a single row and store it in the class. |
||
149 | * |
||
150 | * @param array $line |
||
151 | */ |
||
152 | public function getExampleFromLine(array $line): void |
||
153 | { |
||
154 | foreach ($line as $column => $value) { |
||
155 | $value = trim($value); |
||
156 | if ('' != $value) { |
||
157 | $this->examples[$column][] = $value; |
||
158 | } |
||
159 | } |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * @return array |
||
164 | */ |
||
165 | public function getExamples(): array |
||
166 | { |
||
167 | return $this->examples; |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Return a bunch of examples from the CSV file the user has uploaded. |
||
172 | * |
||
173 | * @param Reader $reader |
||
174 | * @param array $config |
||
175 | * |
||
176 | * @throws FireflyException |
||
177 | */ |
||
178 | public function getExamplesFromFile(Reader $reader, array $config): void |
||
179 | { |
||
180 | $limit = (int)config('csv.example_rows', 5); |
||
181 | $offset = isset($config['has-headers']) && true === $config['has-headers'] ? 1 : 0; |
||
182 | |||
183 | // make statement. |
||
184 | try { |
||
185 | $stmt = (new Statement)->limit($limit)->offset($offset); |
||
186 | // @codeCoverageIgnoreStart |
||
187 | } catch (Exception $e) { |
||
188 | Log::error($e->getMessage()); |
||
189 | throw new FireflyException($e->getMessage()); |
||
190 | } |
||
191 | // @codeCoverageIgnoreEnd |
||
192 | |||
193 | // grab the records: |
||
194 | $records = $stmt->process($reader); |
||
195 | /** @var array $line */ |
||
196 | foreach ($records as $line) { |
||
197 | $line = array_values($line); |
||
198 | $line = $this->processSpecifics($config, $line); |
||
199 | $count = count($line); |
||
200 | $this->totalColumns = $count > $this->totalColumns ? $count : $this->totalColumns; |
||
201 | $this->getExampleFromLine($line); |
||
202 | } |
||
203 | // save column count: |
||
204 | $this->saveColumCount(); |
||
205 | $this->makeExamplesUnique(); |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Get the header row, if one is present. |
||
210 | * |
||
211 | * @param Reader $reader |
||
212 | * @param array $config |
||
213 | * |
||
214 | * @return array |
||
215 | * @throws FireflyException |
||
216 | */ |
||
217 | public function getHeaders(Reader $reader, array $config): array |
||
218 | { |
||
219 | $headers = []; |
||
220 | if (isset($config['has-headers']) && true === $config['has-headers']) { |
||
221 | try { |
||
222 | $stmt = (new Statement)->limit(1)->offset(0); |
||
223 | $records = $stmt->process($reader); |
||
224 | $headers = $records->fetchOne(); |
||
225 | // @codeCoverageIgnoreStart |
||
226 | } catch (Exception $e) { |
||
227 | Log::error($e->getMessage()); |
||
228 | throw new FireflyException($e->getMessage()); |
||
229 | } |
||
230 | // @codeCoverageIgnoreEnd |
||
231 | Log::debug('Detected file headers:', $headers); |
||
232 | } |
||
233 | |||
234 | return $headers; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Get the data necessary to show the configuration screen. |
||
239 | * |
||
240 | * @return array |
||
241 | * @throws FireflyException |
||
242 | */ |
||
243 | public function getNextData(): array |
||
244 | { |
||
245 | try { |
||
246 | $reader = $this->getReader(); |
||
247 | // @codeCoverageIgnoreStart |
||
248 | } catch (Exception $e) { |
||
249 | Log::error($e->getMessage()); |
||
250 | throw new FireflyException($e->getMessage()); |
||
251 | } |
||
252 | // @codeCoverageIgnoreEnd |
||
253 | $configuration = $this->importJob->configuration; |
||
254 | $headers = $this->getHeaders($reader, $configuration); |
||
255 | |||
256 | // get example rows: |
||
257 | $this->getExamplesFromFile($reader, $configuration); |
||
258 | |||
259 | return [ |
||
260 | 'examples' => $this->examples, |
||
261 | 'roles' => $this->getRoles(), |
||
262 | 'total' => $this->totalColumns, |
||
263 | 'headers' => $headers, |
||
264 | ]; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * Return an instance of a CSV file reader so content of the file can be read. |
||
269 | * |
||
270 | * @throws \League\Csv\Exception |
||
271 | */ |
||
272 | public function getReader(): Reader |
||
273 | { |
||
274 | $content = ''; |
||
275 | /** @var Collection $collection */ |
||
276 | $collection = $this->repository->getAttachments($this->importJob); |
||
277 | /** @var Attachment $attachment */ |
||
278 | foreach ($collection as $attachment) { |
||
279 | if ('import_file' === $attachment->filename) { |
||
280 | $content = $this->attachments->getAttachmentContent($attachment); |
||
281 | break; |
||
282 | } |
||
283 | } |
||
284 | $config = $this->repository->getConfiguration($this->importJob); |
||
285 | $reader = Reader::createFromString($content); |
||
286 | $reader->setDelimiter($config['delimiter']); |
||
287 | |||
288 | return $reader; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Returns all possible roles and translate their name. Then sort them. |
||
293 | * |
||
294 | * @codeCoverageIgnore |
||
295 | * @return array |
||
296 | */ |
||
297 | public function getRoles(): array |
||
298 | { |
||
299 | $roles = []; |
||
300 | foreach (array_keys(config('csv.import_roles')) as $role) { |
||
301 | $roles[$role] = (string)trans('import.column_' . $role); |
||
302 | } |
||
303 | asort($roles); |
||
304 | |||
305 | return $roles; |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * If the user has checked columns that cannot be mapped to any value, this function will |
||
310 | * uncheck them and return the configuration again. |
||
311 | * |
||
312 | * @param array $config |
||
313 | * |
||
314 | * @return array |
||
315 | */ |
||
316 | public function ignoreUnmappableColumns(array $config): array |
||
329 | } |
||
330 | |||
331 | /** |
||
332 | * Returns false when it's not necessary to map values. This saves time and is user friendly |
||
333 | * (will skip to the next screen). |
||
334 | * |
||
335 | * @param array $config |
||
336 | * |
||
337 | * @return bool |
||
338 | */ |
||
339 | public function isMappingNecessary(array $config): bool |
||
340 | { |
||
341 | /** @var array $doMapping */ |
||
342 | $doMapping = $config['column-do-mapping'] ?? []; |
||
343 | $toBeMapped = 0; |
||
344 | foreach ($doMapping as $doMap) { |
||
345 | if (true === $doMap) { |
||
346 | ++$toBeMapped; |
||
347 | } |
||
348 | } |
||
349 | |||
350 | return !(0 === $toBeMapped); |
||
351 | } |
||
352 | |||
353 | /** |
||
354 | * Make sure that the examples do not contain double data values. |
||
355 | */ |
||
356 | public function makeExamplesUnique(): void |
||
357 | { |
||
358 | foreach ($this->examples as $index => $values) { |
||
359 | $this->examples[$index] = array_unique($values); |
||
360 | } |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * if the user has configured specific fixes to be applied, they must be applied to the example data as well. |
||
365 | * |
||
366 | * @param array $config |
||
367 | * @param array $line |
||
368 | * |
||
369 | * @return array |
||
370 | */ |
||
371 | public function processSpecifics(array $config, array $line): array |
||
372 | { |
||
373 | $validSpecifics = array_keys(config('csv.import_specifics')); |
||
374 | $specifics = $config['specifics'] ?? []; |
||
375 | $names = array_keys($specifics); |
||
376 | foreach ($names as $name) { |
||
377 | if (!in_array($name, $validSpecifics, true)) { |
||
378 | continue; |
||
379 | } |
||
380 | /** @var SpecificInterface $specific */ |
||
381 | $specific = app('FireflyIII\Import\Specifics\\' . $name); |
||
382 | $line = $specific->run($line); |
||
383 | } |
||
384 | |||
385 | return $line; |
||
386 | |||
387 | } |
||
388 | |||
389 | /** |
||
390 | * Save the column count in the job. It's used in a later stage. |
||
391 | * |
||
392 | * @return void |
||
393 | */ |
||
394 | public function saveColumCount(): void |
||
395 | { |
||
396 | $config = $this->importJob->configuration; |
||
397 | $config['column-count'] = $this->totalColumns; |
||
398 | $this->repository->setConfiguration($this->importJob, $config); |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * Set job and some start values. |
||
403 | * |
||
404 | * @param ImportJob $importJob |
||
405 | */ |
||
406 | public function setImportJob(ImportJob $importJob): void |
||
414 | } |
||
415 | } |
||
416 |
This interface has been deprecated. The supplier of the interface has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.