Complex classes like Reader 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 Reader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
51 | class Reader extends AbstractCsv implements Countable, IteratorAggregate, JsonSerializable |
||
52 | { |
||
53 | /** |
||
54 | * header offset. |
||
55 | * |
||
56 | * @var int|null |
||
57 | */ |
||
58 | protected $header_offset; |
||
59 | |||
60 | /** |
||
61 | * header record. |
||
62 | * |
||
63 | * @var string[] |
||
64 | */ |
||
65 | protected $header = []; |
||
66 | |||
67 | /** |
||
68 | * records count. |
||
69 | * |
||
70 | * @var int |
||
71 | */ |
||
72 | protected $nb_records = -1; |
||
73 | |||
74 | /** |
||
75 | * {@inheritdoc} |
||
76 | */ |
||
77 | protected $stream_filter_mode = STREAM_FILTER_READ; |
||
78 | |||
79 | /** |
||
80 | * @var bool |
||
81 | */ |
||
82 | protected $is_empty_records_included = false; |
||
83 | |||
84 | /** |
||
85 | * {@inheritdoc} |
||
86 | */ |
||
87 | 3 | public static function createFromPath(string $path, string $open_mode = 'r', $context = null) |
|
113 | |||
114 | /** |
||
115 | * {@inheritdoc} |
||
116 | */ |
||
117 | protected function resetProperties() |
||
123 | 24 | ||
124 | 18 | /** |
|
125 | * Returns the header offset. |
||
126 | * |
||
127 | 9 | * If no CSV header offset is set this method MUST return null |
|
128 | 3 | * |
|
129 | * @return int|null |
||
130 | */ |
||
131 | 9 | public function getHeaderOffset() |
|
135 | |||
136 | /** |
||
137 | * Returns the CSV record used as header. |
||
138 | * |
||
139 | * The returned header is represented as an array of string values |
||
140 | * |
||
141 | * @return string[] |
||
142 | */ |
||
143 | 12 | public function getHeader(): array |
|
157 | |||
158 | /** |
||
159 | * Determine the CSV record header. |
||
160 | * |
||
161 | * @throws Exception If the header offset is set and no record is found or is the empty array |
||
162 | 12 | * |
|
163 | * @return string[] |
||
164 | 12 | */ |
|
165 | 12 | protected function setHeader(int $offset): array |
|
178 | 21 | ||
179 | 6 | /** |
|
180 | * Returns the row at a given offset. |
||
181 | 6 | * |
|
182 | * @return array|false |
||
183 | */ |
||
184 | 15 | protected function seekRow(int $offset) |
|
194 | |||
195 | /** |
||
196 | * Returns the document as an Iterator. |
||
197 | */ |
||
198 | 12 | protected function getDocument(): Iterator |
|
212 | |||
213 | /** |
||
214 | * Strip the BOM sequence from a record. |
||
215 | * |
||
216 | * @param string[] $record |
||
217 | 9 | * |
|
218 | * @return string[] |
||
219 | 9 | */ |
|
220 | 9 | protected function removeBOM(array $record, int $bom_length, string $enclosure): array |
|
235 | |||
236 | 3 | /** |
|
237 | * {@inheritdoc} |
||
238 | */ |
||
239 | public function __call($method, array $arguments) |
||
248 | |||
249 | /** |
||
250 | 3 | * {@inheritdoc} |
|
251 | */ |
||
252 | 3 | public function count(): int |
|
260 | |||
261 | /** |
||
262 | * {@inheritdoc} |
||
263 | */ |
||
264 | public function getIterator(): Iterator |
||
268 | |||
269 | 36 | /** |
|
270 | * {@inheritdoc} |
||
271 | 36 | */ |
|
272 | 33 | public function jsonSerialize(): array |
|
276 | 33 | ||
277 | 33 | /** |
|
278 | 30 | * Returns the CSV file path after uncompressed the file. |
|
279 | * |
||
280 | * Supported compressions are zip, gz |
||
281 | 33 | * |
|
282 | 33 | * @param String - File path of the input CSV compressed file |
|
283 | 33 | */ |
|
284 | 18 | public function decompressCSV(String $path): String |
|
303 | |||
304 | /** |
||
305 | * Returns the CSV records as an iterator object. |
||
306 | * |
||
307 | * Each CSV record is represented as a simple array containing strings or null values. |
||
308 | * |
||
309 | * If the CSV document has a header record then each record is combined |
||
310 | * to the header record and the header record is removed from the iterator. |
||
311 | * |
||
312 | * If the CSV document is inconsistent. Missing record fields are |
||
313 | 30 | * filled with null values while extra record fields are strip from |
|
314 | * the returned object. |
||
315 | 30 | * |
|
316 | 27 | * @param string[] $header an optional header to use instead of the CSV document header |
|
317 | */ |
||
318 | public function getRecords(array $header = []): Iterator |
||
352 | 30 | ||
353 | /** |
||
354 | 30 | * Returns the header to be used for iteration. |
|
355 | 21 | * |
|
356 | * @param string[] $header |
||
357 | * |
||
358 | 9 | * @throws Exception If the header contains non unique column name |
|
359 | 9 | * |
|
360 | 9 | * @return string[] |
|
361 | 3 | */ |
|
362 | protected function computeHeader(array $header) |
||
374 | |||
375 | /** |
||
376 | * Combine the CSV header to each record if present. |
||
377 | * |
||
378 | * @param string[] $header |
||
379 | */ |
||
380 | protected function combineHeader(Iterator $iterator, array $header): Iterator |
||
397 | 6 | ||
398 | /** |
||
399 | 6 | * Strip the BOM sequence from the returned records if necessary. |
|
400 | */ |
||
401 | protected function stripBOM(Iterator $iterator, string $bom): Iterator |
||
418 | 12 | ||
419 | /** |
||
420 | 12 | * Selects the record to be used as the CSV header. |
|
421 | 12 | * |
|
422 | 12 | * Because the header is represented as an array, to be valid |
|
423 | * a header MUST contain only unique string value. |
||
424 | * |
||
425 | 12 | * @param int|null $offset the header record offset |
|
426 | * |
||
427 | * @throws Exception if the offset is a negative integer |
||
428 | * |
||
429 | * @return static |
||
430 | */ |
||
431 | 12 | public function setHeaderOffset($offset): self |
|
450 | |||
451 | /** |
||
452 | * Enable skipping empty records. |
||
453 | */ |
||
454 | public function skipEmptyRecords(): self |
||
463 | |||
464 | /** |
||
465 | * Disable skipping empty records. |
||
466 | */ |
||
467 | public function includeEmptyRecords(): self |
||
476 | |||
477 | /** |
||
478 | * Tells whether empty records are skipped by the instance. |
||
479 | */ |
||
480 | public function isEmptyRecordsIncluded(): bool |
||
484 | } |
||
485 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.