Skip to content

Commit a7e5828

Browse files
authored
Merge pull request #56 from keboola/CM-873-php-csv-may-detect-linebreak-incorrectly-in-edge-case
CM-873 May detect linebreak incorrectly in edge case
2 parents b97eb09 + 0099173 commit a7e5828

14 files changed

+78
-42
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.clrf.csv eol=crlf

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
},
3030
"require-dev": {
3131
"ext-json": "*",
32-
"keboola/coding-standard": "^13.0",
32+
"keboola/coding-standard": "^15.0",
3333
"php-parallel-lint/php-parallel-lint": "^1.3",
34-
"phpstan/phpstan": "^1.4",
34+
"phpstan/phpstan": "^1.10",
3535
"phpunit/phpunit": ">=7.5 <=9.6",
36-
"phpstan/phpdoc-parser": "1.5.*"
36+
"phpstan/phpdoc-parser": "^1.25"
3737
},
3838
"scripts": {
3939
"phpstan": "phpstan analyse ./src ./tests --level=max --no-progress -c phpstan.neon",

docker-compose.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ version: "3"
22

33
services:
44

5-
dev:
5+
dev: &dev
66
build: .
77
tty: true
88
volumes:
99
- ./:/code
10-
working_dir: /code
10+
working_dir: /code
11+
dev-xdebug:
12+
<<: *dev
13+
build:
14+
context: docker/xdebug

docker/xdebug/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM php-csv-dev
2+
3+
RUN pecl install xdebug-2.9.8 \
4+
&& docker-php-ext-enable xdebug

phpcs.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@
2121
<rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
2222
<exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing"/>
2323
</rule>
24-
</ruleset>
24+
<rule ref="SlevomatCodingStandard.Functions.RequireTrailingCommaInDeclaration">
25+
<exclude name="SlevomatCodingStandard.Functions.RequireTrailingCommaInDeclaration.MissingTrailingComma"/>
26+
</rule>
27+
</ruleset>

phpstan-baseline-8+.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ parameters:
275275
count: 1
276276
path: tests/CsvReadTest.php
277277

278+
-
279+
message: "#^Method Keboola\\\\Csv\\\\Tests\\\\CsvReadTest\\:\\:testNewlineDetectionEdgecaseWithCrLf\\(\\) has no return type specified\\.$#"
280+
count: 1
281+
path: tests/CsvReadTest.php
282+
278283
-
279284
message: "#^Method Keboola\\\\Csv\\\\Tests\\\\CsvReadTest\\:\\:testParse\\(\\) has no return type specified\\.$#"
280285
count: 1

phpstan-baseline.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ parameters:
280280
count: 1
281281
path: tests/CsvReadTest.php
282282

283+
-
284+
message: "#^Method Keboola\\\\Csv\\\\Tests\\\\CsvReadTest\\:\\:testNewlineDetectionEdgecaseWithCrLf\\(\\) has no return type specified\\.$#"
285+
count: 1
286+
path: tests/CsvReadTest.php
287+
283288
-
284289
message: "#^Method Keboola\\\\Csv\\\\Tests\\\\CsvReadTest\\:\\:testParse\\(\\) has no return type specified\\.$#"
285290
count: 1

src/CsvOptions.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected function validateEnclosure($enclosure)
5050
if (strlen($enclosure) > 1) {
5151
throw new InvalidArgumentException(
5252
'Enclosure must be a single character. ' . json_encode($enclosure) . ' received',
53-
Exception::INVALID_PARAM
53+
Exception::INVALID_PARAM,
5454
);
5555
}
5656
}
@@ -64,14 +64,14 @@ protected function validateDelimiter($delimiter)
6464
if (strlen($delimiter) > 1) {
6565
throw new InvalidArgumentException(
6666
'Delimiter must be a single character. ' . json_encode($delimiter) . ' received',
67-
Exception::INVALID_PARAM
67+
Exception::INVALID_PARAM,
6868
);
6969
}
7070

7171
if (strlen($delimiter) === 0) {
7272
throw new InvalidArgumentException(
7373
'Delimiter cannot be empty.',
74-
Exception::INVALID_PARAM
74+
Exception::INVALID_PARAM,
7575
);
7676
}
7777
}

src/CsvReader.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class CsvReader extends AbstractCsvFile implements Iterator
1313
* @deprecated use Keboola\Csv\CsvOptions::DEFAULT_ENCLOSURE
1414
*/
1515
const DEFAULT_ESCAPED_BY = CsvOptions::DEFAULT_ESCAPED_BY;
16+
const SAMPLE_SIZE = 10000;
1617

1718
/**
1819
* @var int
@@ -92,7 +93,7 @@ protected function validateSkipLines($skipLines)
9293
if (!is_int($skipLines) || $skipLines < 0) {
9394
throw new InvalidArgumentException(
9495
"Number of lines to skip must be a positive integer. \"$skipLines\" received.",
95-
Exception::INVALID_PARAM
96+
Exception::INVALID_PARAM,
9697
);
9798
}
9899
}
@@ -106,14 +107,14 @@ protected function openCsvFile($fileName)
106107
if (!is_file($fileName)) {
107108
throw new Exception(
108109
'Cannot open file ' . $fileName,
109-
Exception::FILE_NOT_EXISTS
110+
Exception::FILE_NOT_EXISTS,
110111
);
111112
}
112113
$this->filePointer = @fopen($fileName, 'r');
113114
if (!$this->filePointer) {
114115
throw new Exception(
115116
"Cannot open file {$fileName} " . error_get_last()['message'],
116-
Exception::FILE_NOT_EXISTS
117+
Exception::FILE_NOT_EXISTS,
117118
);
118119
}
119120
}
@@ -124,7 +125,12 @@ protected function openCsvFile($fileName)
124125
protected function detectLineBreak()
125126
{
126127
@rewind($this->getFilePointer());
127-
$sample = @fread($this->getFilePointer(), 10000);
128+
$sample = @fread($this->getFilePointer(), self::SAMPLE_SIZE);
129+
if (substr((string) $sample, -1) === "\r") {
130+
// we might have hit the file in the middle of CR+LF, only getting CR
131+
@rewind($this->getFilePointer());
132+
$sample = @fread($this->getFilePointer(), self::SAMPLE_SIZE+1);
133+
}
128134

129135
return LineBreaksHelper::detectLineBreaks($sample, $this->getEnclosure(), $this->getEscapedBy());
130136
}
@@ -155,7 +161,7 @@ protected function validateLineBreak()
155161

156162
throw new InvalidArgumentException(
157163
"Invalid line break. Please use unix \\n or win \\r\\n line breaks.",
158-
Exception::INVALID_PARAM
164+
Exception::INVALID_PARAM,
159165
);
160166
}
161167

src/CsvWriter.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ private function validateLineBreak($lineBreak)
5555
throw new Exception(
5656
'Invalid line break: ' . json_encode($lineBreak) .
5757
' allowed line breaks: ' . json_encode($allowedLineBreaks),
58-
Exception::INVALID_PARAM
58+
Exception::INVALID_PARAM,
5959
);
6060
}
6161
}
@@ -72,14 +72,14 @@ protected function openCsvFile($fileName)
7272
throw new Exception(
7373
"Cannot open file {$fileName} " . $e->getMessage(),
7474
Exception::FILE_NOT_EXISTS,
75-
$e
75+
$e,
7676
);
7777
}
7878

7979
if (!$this->filePointer) {
8080
throw new Exception(
8181
"Cannot open file {$fileName} " . error_get_last()['message'],
82-
Exception::FILE_NOT_EXISTS
82+
Exception::FILE_NOT_EXISTS,
8383
);
8484
}
8585
}
@@ -100,7 +100,7 @@ public function writeRow(array $row)
100100
' Return: false' .
101101
' To write: ' . strlen($str) . ' Written: 0',
102102
Exception::WRITE_ERROR,
103-
$e
103+
$e,
104104
);
105105
}
106106

@@ -114,7 +114,7 @@ public function writeRow(array $row)
114114
($ret === false && error_get_last() ? 'Error: ' . error_get_last()['message'] : '') .
115115
' Return: ' . json_encode($ret) .
116116
' To write: ' . strlen($str) . ' Written: ' . (int) $ret,
117-
Exception::WRITE_ERROR
117+
Exception::WRITE_ERROR,
118118
);
119119
}
120120
}
@@ -138,7 +138,7 @@ public function rowToStr(array $row)
138138
)) {
139139
throw new Exception(
140140
'Cannot write data into column: ' . var_export($column, true),
141-
Exception::WRITE_ERROR
141+
Exception::WRITE_ERROR,
142142
);
143143
}
144144

0 commit comments

Comments
 (0)