1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author: stev leibelt <[email protected]> |
4
|
|
|
* @since: 2015-04-17 |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Net\Bazzline\Component\Csv\Reader; |
8
|
|
|
|
9
|
|
|
//@see https://github.com/ajgarlag/AjglCsv/blob/master/Reader/ReaderAbstract.php |
10
|
|
|
//@see https://github.com/jwage/easy-csv/blob/master/lib/EasyCSV/Reader.php |
11
|
|
|
//@todo implement save version to call enable/disable headline before setDelimiter etc. |
12
|
|
|
use Net\Bazzline\Component\Csv\AbstractBase; |
13
|
|
|
use Net\Bazzline\Component\Csv\InvalidArgumentException; |
14
|
|
|
use Net\Bazzline\Component\Toolbox\HashMap\Combine; |
15
|
|
|
use SplFileObject; |
16
|
|
|
|
17
|
|
|
class Reader extends AbstractBase implements ReaderInterface |
18
|
|
|
{ |
19
|
|
|
/** @var bool */ |
20
|
|
|
private $addHeadlineToOutput = true; |
21
|
|
|
|
22
|
|
|
/** @var Combine */ |
23
|
|
|
private $combine; |
24
|
|
|
|
25
|
|
|
/** @var int */ |
26
|
|
|
private $initialLineNumber = 0; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @param null $currentLineNumber |
30
|
|
|
* @return array|bool|string |
31
|
|
|
*/ |
32
|
|
|
public function __invoke($currentLineNumber = null) |
33
|
|
|
{ |
34
|
|
|
return $this->readOne($currentLineNumber); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
//begin of AbstractBase |
38
|
|
|
/** |
39
|
|
|
* @param string $delimiter |
40
|
|
|
* @throws InvalidArgumentException |
41
|
|
|
*/ |
42
|
|
|
public function setDelimiter($delimiter) |
43
|
|
|
{ |
44
|
|
|
parent::setDelimiter($delimiter); |
45
|
|
|
if ($this->hasHeadline()) { |
46
|
|
|
$this->enableHasHeadline(); |
47
|
|
|
} |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @param string $enclosure |
52
|
|
|
* @throws InvalidArgumentException |
53
|
|
|
*/ |
54
|
|
|
public function setEnclosure($enclosure) |
55
|
|
|
{ |
56
|
|
|
parent::setEnclosure($enclosure); |
57
|
|
|
if ($this->hasHeadline()) { |
58
|
|
|
$this->enableHasHeadline(); |
59
|
|
|
} |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @param string $escapeCharacter |
64
|
|
|
* @throws InvalidArgumentException |
65
|
|
|
*/ |
66
|
|
|
public function setEscapeCharacter($escapeCharacter) |
67
|
|
|
{ |
68
|
|
|
parent::setEscapeCharacter($escapeCharacter); |
69
|
|
|
if ($this->hasHeadline()) { |
70
|
|
|
$this->enableHasHeadline(); |
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
//end of AbstractBase |
75
|
|
|
|
76
|
|
|
//begin of Iterator |
77
|
|
|
/** |
78
|
|
|
* (PHP 5 >= 5.0.0)<br/> |
79
|
|
|
* Return the current element |
80
|
|
|
* @link http://php.net/manual/en/iterator.current.php |
81
|
|
|
* @return mixed Can return any type. |
82
|
|
|
*/ |
83
|
|
|
public function current() |
84
|
|
|
{ |
85
|
|
|
return $this->getFileHandler()->current(); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* (PHP 5 >= 5.0.0)<br/> |
90
|
|
|
* Move forward to next element |
91
|
|
|
* @link http://php.net/manual/en/iterator.next.php |
92
|
|
|
* @return void Any returned value is ignored. |
93
|
|
|
*/ |
94
|
|
|
public function next() |
95
|
|
|
{ |
96
|
|
|
$this->getFileHandler()->next(); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* (PHP 5 >= 5.0.0)<br/> |
101
|
|
|
* Return the key of the current element |
102
|
|
|
* @link http://php.net/manual/en/iterator.key.php |
103
|
|
|
* @return mixed scalar on success, or null on failure. |
104
|
|
|
*/ |
105
|
|
|
public function key() |
106
|
|
|
{ |
107
|
|
|
return $this->getFileHandler()->key(); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* (PHP 5 >= 5.0.0)<br/> |
112
|
|
|
* Checks if current position is valid |
113
|
|
|
* @link http://php.net/manual/en/iterator.valid.php |
114
|
|
|
* @return boolean The return value will be casted to boolean and then evaluated. |
115
|
|
|
* Returns true on success or false on failure. |
116
|
|
|
*/ |
117
|
|
|
public function valid() |
118
|
|
|
{ |
119
|
|
|
return $this->getFileHandler()->valid(); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* (PHP 5 >= 5.0.0)<br/> |
124
|
|
|
* Rewind the Iterator to the first element |
125
|
|
|
* @link http://php.net/manual/en/iterator.rewind.php |
126
|
|
|
* @return void Any returned value is ignored. |
127
|
|
|
*/ |
128
|
|
|
public function rewind() |
129
|
|
|
{ |
130
|
|
|
if ($this->hasHeadline()) { |
131
|
|
|
$this->updateHeadline(); |
132
|
|
|
$lineNumber = 1; |
133
|
|
|
} else { |
134
|
|
|
$lineNumber = 0; |
135
|
|
|
} |
136
|
|
|
$this->initialLineNumber = $lineNumber; |
137
|
|
|
$this->seekFileToCurrentLineNumberIfNeeded( |
138
|
|
|
$this->getFileHandler(), |
|
|
|
|
139
|
|
|
$lineNumber |
140
|
|
|
); |
141
|
|
|
} |
142
|
|
|
//end of Iterator |
143
|
|
|
|
144
|
|
|
//begin of headlines |
145
|
|
|
/** |
146
|
|
|
* @return $this |
147
|
|
|
*/ |
148
|
|
|
public function disableAddHeadlineToOutput() |
149
|
|
|
{ |
150
|
|
|
$this->addHeadlineToOutput = false; |
151
|
|
|
|
152
|
|
|
return $this; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* @return $this |
157
|
|
|
*/ |
158
|
|
|
public function enableAddHeadlineToOutput() |
159
|
|
|
{ |
160
|
|
|
$this->addHeadlineToOutput = true; |
161
|
|
|
|
162
|
|
|
return $this; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @return $this |
167
|
|
|
*/ |
168
|
|
|
public function disableHasHeadline() |
169
|
|
|
{ |
170
|
|
|
$this->resetHeadline(); |
171
|
|
|
$this->rewind(); |
172
|
|
|
|
173
|
|
|
return $this; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @return $this |
178
|
|
|
*/ |
179
|
|
|
public function enableHasHeadline() |
180
|
|
|
{ |
181
|
|
|
$this->updateHeadline(); |
182
|
|
|
$this->rewind(); |
183
|
|
|
|
184
|
|
|
return $this; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
private function updateHeadline() |
188
|
|
|
{ |
189
|
|
|
$this->initialLineNumber = 0; |
190
|
|
|
$wasEnabled = $this->addHeadlineToOutput; |
191
|
|
|
|
192
|
|
|
if ($wasEnabled) { |
193
|
|
|
$this->disableAddHeadlineToOutput(); |
194
|
|
|
} |
195
|
|
|
$this->setHeadline($this->readOne(0)); |
196
|
|
|
if ($wasEnabled) { |
197
|
|
|
$this->enableAddHeadlineToOutput(); |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @return false|array |
203
|
|
|
*/ |
204
|
|
|
public function readHeadline() |
205
|
|
|
{ |
206
|
|
|
return $this->getHeadline(); |
207
|
|
|
} |
208
|
|
|
//end of headlines |
209
|
|
|
|
210
|
|
|
//begin of general |
211
|
|
|
/** |
212
|
|
|
* @param Combine $combine |
213
|
|
|
* @return $this |
214
|
|
|
*/ |
215
|
|
|
public function setCombine(Combine $combine) |
216
|
|
|
{ |
217
|
|
|
$this->combine = $combine; |
218
|
|
|
|
219
|
|
|
return $this; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @param null|int $lineNumber - if "null", current line number is used |
224
|
|
|
* @return array|bool|string |
225
|
|
|
*/ |
226
|
|
|
public function readOne($lineNumber = null) |
227
|
|
|
{ |
228
|
|
|
$file = $this->getFileHandler(); |
229
|
|
|
$headline = $this->getHeadline(); |
230
|
|
|
$hasHeadline = $this->hasHeadline(); |
231
|
|
|
$this->seekFileToCurrentLineNumberIfNeeded($file, $lineNumber); |
|
|
|
|
232
|
|
|
|
233
|
|
|
$addHeadline = ($hasHeadline && $this->addHeadlineToOutput && ($this->current() !== false)); |
234
|
|
|
$content = ($addHeadline) |
235
|
|
|
? $this->combine->combine($headline, $this->current()) |
|
|
|
|
236
|
|
|
: $this->current(); |
237
|
|
|
$this->next(); |
238
|
|
|
|
239
|
|
|
return $content; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* @param int $length |
244
|
|
|
* @param null|int $lineNumberToStartWith - if "null", current line number is used |
245
|
|
|
* @return array |
246
|
|
|
*/ |
247
|
|
|
public function readMany($length, $lineNumberToStartWith = null) |
248
|
|
|
{ |
249
|
|
|
$this->rewind(); |
250
|
|
|
$lastLine = $lineNumberToStartWith + $length; |
251
|
|
|
$lines = []; |
252
|
|
|
$currentLine = $lineNumberToStartWith; |
253
|
|
|
|
254
|
|
|
//foreach not usable here since it is calling rewind before iterating |
255
|
|
|
while ($currentLine < $lastLine) { |
256
|
|
|
$line = $this->readOne($currentLine); |
257
|
|
|
$lines = $this->addToLinesIfLineIsValid($lines, $line); |
258
|
|
|
if (!$this->valid()) { |
259
|
|
|
$currentLine = $lastLine; |
260
|
|
|
} |
261
|
|
|
++$currentLine; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
return $lines; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* @return array |
269
|
|
|
*/ |
270
|
|
|
public function readAll() |
271
|
|
|
{ |
272
|
|
|
$this->rewind(); |
273
|
|
|
$lines = []; |
274
|
|
|
|
275
|
|
|
while ($line = $this()) { |
276
|
|
|
$lines = $this->addToLinesIfLineIsValid($lines, $line); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
return $lines; |
280
|
|
|
} |
281
|
|
|
//end of general |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* @return string |
285
|
|
|
*/ |
286
|
|
|
protected function getFileHandlerOpenMode() |
287
|
|
|
{ |
288
|
|
|
return 'r'; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* @param array $lines |
293
|
|
|
* @param mixed $line |
294
|
|
|
* @return array |
295
|
|
|
*/ |
296
|
|
|
private function addToLinesIfLineIsValid(array &$lines, $line) |
297
|
|
|
{ |
298
|
|
|
if (!is_null($line)) { |
299
|
|
|
$lines[] = $line; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
return $lines; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* @param SplFileObject $file |
307
|
|
|
* @param null|int $newLineNumber |
308
|
|
|
* @return SplFileObject |
309
|
|
|
*/ |
310
|
|
|
private function seekFileToCurrentLineNumberIfNeeded(SplFileObject $file, $newLineNumber = null) |
311
|
|
|
{ |
312
|
|
|
$seekIsNeeded = ((!is_null($newLineNumber)) |
313
|
|
|
&& ($newLineNumber >= $this->initialLineNumber) |
314
|
|
|
&& ($newLineNumber !== $this->key())); |
315
|
|
|
|
316
|
|
|
if ($seekIsNeeded) { |
317
|
|
|
$file->seek($newLineNumber); |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
return $file; |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.