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 |
||
36 | class Reader extends AbstractCsv implements Countable, IteratorAggregate, JsonSerializable |
||
37 | { |
||
38 | /** |
||
39 | * header offset |
||
40 | * |
||
41 | * @var int|null |
||
42 | */ |
||
43 | protected $header_offset; |
||
44 | |||
45 | /** |
||
46 | * header record |
||
47 | * |
||
48 | * @var string[] |
||
49 | */ |
||
50 | protected $header = []; |
||
51 | |||
52 | /** |
||
53 | * records count |
||
54 | * |
||
55 | * @var int |
||
56 | */ |
||
57 | protected $nb_records = -1; |
||
58 | |||
59 | /** |
||
60 | * {@inheritdoc} |
||
61 | */ |
||
62 | protected $stream_filter_mode = STREAM_FILTER_READ; |
||
63 | |||
64 | /** |
||
65 | * {@inheritdoc} |
||
66 | */ |
||
67 | 2 | public static function createFromPath(string $path, string $open_mode = 'r', $context = null): AbstractCsv |
|
71 | |||
72 | /** |
||
73 | * Returns the header offset |
||
74 | * |
||
75 | * If no CSV header offset is set this method MUST return null |
||
76 | * |
||
77 | * @return int|null |
||
78 | */ |
||
79 | 10 | public function getHeaderOffset() |
|
83 | |||
84 | /** |
||
85 | * Returns the CSV record used as header |
||
86 | * |
||
87 | * The returned header is represented as an array of string values |
||
88 | * |
||
89 | * @return string[] |
||
90 | */ |
||
91 | 10 | public function getHeader(): array |
|
105 | |||
106 | /** |
||
107 | * Determine the CSV record header |
||
108 | * |
||
109 | * @param int $offset |
||
110 | * |
||
111 | * @throws Exception If the header offset is set and no record is found or is the empty array |
||
112 | * |
||
113 | * @return string[] |
||
114 | */ |
||
115 | 8 | protected function setHeader(int $offset): array |
|
128 | |||
129 | /** |
||
130 | * Returns the row at a given offset |
||
131 | * |
||
132 | * @param int $offset |
||
133 | * |
||
134 | * @return mixed |
||
135 | */ |
||
136 | 4 | protected function seekRow(int $offset) |
|
137 | { |
||
138 | 4 | $this->document->setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY); |
|
139 | 4 | $this->document->setCsvControl($this->delimiter, $this->enclosure, $this->escape); |
|
140 | 4 | $this->document->rewind(); |
|
141 | |||
142 | //Workaround for SplFileObject::seek bug in PHP7.2+ see https://bugs.php.net/bug.php?id=75917 |
||
143 | 4 | if (PHP_VERSION_ID >= 70200 && !$this->document instanceof Stream) { |
|
144 | 1 | while ($offset !== $this->document->key() && $this->document->valid()) { |
|
145 | 1 | $this->document->next(); |
|
146 | } |
||
147 | |||
148 | 1 | return $this->document->current(); |
|
149 | } |
||
150 | |||
151 | 3 | $this->document->seek($offset); |
|
152 | |||
153 | 3 | return $this->document->current(); |
|
154 | } |
||
155 | |||
156 | /** |
||
157 | * Strip the BOM sequence from a record |
||
158 | * |
||
159 | * @param string[] $record |
||
160 | * @param int $bom_length |
||
161 | * @param string $enclosure |
||
162 | * |
||
163 | * @return string[] |
||
164 | */ |
||
165 | 8 | protected function removeBOM(array $record, int $bom_length, string $enclosure): array |
|
180 | |||
181 | /** |
||
182 | * {@inheritdoc} |
||
183 | */ |
||
184 | 6 | public function __call($method, array $arguments) |
|
193 | |||
194 | /** |
||
195 | * {@inheritdoc} |
||
196 | */ |
||
197 | 2 | public function count(): int |
|
205 | |||
206 | /** |
||
207 | * {@inheritdoc} |
||
208 | */ |
||
209 | 2 | public function getIterator(): Iterator |
|
213 | |||
214 | /** |
||
215 | * {@inheritdoc} |
||
216 | */ |
||
217 | 2 | public function jsonSerialize(): array |
|
221 | |||
222 | /** |
||
223 | * Returns the CSV records as an iterator object. |
||
224 | * |
||
225 | * Each CSV record is represented as a simple array containig strings or null values. |
||
226 | * |
||
227 | * If the CSV document has a header record then each record is combined |
||
228 | * to the header record and the header record is removed from the iterator. |
||
229 | * |
||
230 | * If the CSV document is inconsistent. Missing record fields are |
||
231 | * filled with null values while extra record fields are strip from |
||
232 | * the returned object. |
||
233 | * |
||
234 | * @param string[] $header an optional header to use instead of the CSV document header |
||
235 | * |
||
236 | * @return Iterator |
||
237 | */ |
||
238 | 12 | public function getRecords(array $header = []): Iterator |
|
257 | |||
258 | /** |
||
259 | * Returns the header to be used for iteration |
||
260 | * |
||
261 | * @param string[] $header |
||
262 | * |
||
263 | * @throws Exception If the header contains non unique column name |
||
264 | * |
||
265 | * @return string[] |
||
266 | */ |
||
267 | 16 | protected function computeHeader(array $header) |
|
279 | |||
280 | /** |
||
281 | * Combine the CSV header to each record if present |
||
282 | * |
||
283 | * @param Iterator $iterator |
||
284 | * @param string[] $header |
||
285 | * |
||
286 | * @return Iterator |
||
287 | */ |
||
288 | 20 | protected function combineHeader(Iterator $iterator, array $header): Iterator |
|
305 | |||
306 | /** |
||
307 | * Strip the BOM sequence from the returned records if necessary |
||
308 | * |
||
309 | * @param Iterator $iterator |
||
310 | * @param string $bom |
||
311 | * |
||
312 | * @return Iterator |
||
313 | */ |
||
314 | 16 | protected function stripBOM(Iterator $iterator, string $bom): Iterator |
|
331 | |||
332 | /** |
||
333 | * Selects the record to be used as the CSV header |
||
334 | * |
||
335 | * Because the header is represented as an array, to be valid |
||
336 | * a header MUST contain only unique string value. |
||
337 | * |
||
338 | * @param int|null $offset the header record offset |
||
339 | * |
||
340 | * @throws Exception if the offset is a negative integer |
||
341 | * |
||
342 | * @return static |
||
343 | */ |
||
344 | 14 | public function setHeaderOffset($offset): self |
|
363 | |||
364 | /** |
||
365 | * {@inheritdoc} |
||
366 | */ |
||
367 | 8 | protected function resetProperties() |
|
372 | } |
||
373 |