Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5a25d00
refactor: reorganize tests into reader and writer tests
odinuv Apr 16, 2018
fb574c8
refactor: reorganize tests into reader and writer tests
odinuv Apr 16, 2018
9018818
refactor: reorganize tests into reader and writer tests
odinuv Apr 16, 2018
68ab0a6
refactor: split into reader and writer classes
odinuv Apr 17, 2018
ca998d7
refactor: split into CSVReader and CSVWriter
odinuv Apr 23, 2018
19e0c81
refactor: remove lazy initialization
odinuv Apr 23, 2018
1f1589c
tests: fix invalid line break test
odinuv Apr 23, 2018
92f45cb
chore: cs fixes
odinuv Apr 23, 2018
fe2a040
fix: remove + modes
odinuv Apr 26, 2018
3c5325a
fix: use correct defaults
odinuv Apr 26, 2018
2161707
tests: polished
odinuv Apr 26, 2018
4179ac8
tests: polished
odinuv Apr 26, 2018
ca0e242
tests: polished
odinuv Apr 26, 2018
17fc0cc
fix: use correct exception params
odinuv Apr 28, 2018
fda01c0
refactor: remove exception context params, because they are not reall…
odinuv Apr 28, 2018
92e590f
chore: remove unused variable
odinuv Apr 28, 2018
9a107c5
docs: update
odinuv Apr 29, 2018
9d5ca09
refactor: remove dependency on splfileinfo
odinuv May 1, 2018
9387be9
refactor: make fields private
odinuv May 1, 2018
7325572
feat: no hardcoded line break
odinuv May 1, 2018
69c84b8
refactor: remove string error codes and use standard exception
odinuv May 4, 2018
ed78d27
fix: close only owned file pointer
odinuv May 4, 2018
c5b9822
refactor: no need to have adjustable mode since file pointer can now …
odinuv May 4, 2018
d09b013
polished
odinuv May 4, 2018
1661411
rearrange code
odinuv May 4, 2018
e1d5fe7
docs: add example
odinuv May 4, 2018
035e737
refactor: move file handling code to AbstractCsvFile
odinuv May 4, 2018
537dbb0
tests: simplify
odinuv May 4, 2018
7bd505d
chore: cs fixes
odinuv May 4, 2018
f68c799
fix: better error messages when weird characters are sent as delimite…
odinuv May 12, 2018
5b9ccb0
fix: better and more verbose error message on failed write
odinuv May 12, 2018
eb55800
Merge branch 'master' of https://github.com/keboola/php-csv into odin…
odinuv May 13, 2018
572371a
fix: better error message on failed fwrite, also don't spit notice if…
odinuv May 13, 2018
a101d2f
fix: more robust error checking
odinuv May 14, 2018
44a4914
tests: portable error message
odinuv May 14, 2018
bb673a8
tests: more portable error message
odinuv May 14, 2018
0916f78
tests: catch notices
odinuv May 14, 2018
3bc11a7
Merge branch 'master' of https://github.com/keboola/php-csv into odin…
odinuv May 14, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/AbstractCsvFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

namespace Keboola\Csv;

use SplFileInfo;

class AbstractCsvFile extends SplFileInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Když už se to refaktoruje tak si myslím že by to němělo dědit ten SplFileInfo, ale měl by být jako private property. To by mohlo umožnit např. založit ten reader/writer už z existujícího file resource. Na což jsem párkrát narazil a jsou na to i issues #14 a #5

{
const DEFAULT_DELIMITER = ',';
const DEFAULT_ENCLOSURE = '"';

/**
* @var string
*/
protected $delimiter;

/**
* @var string
*/
protected $enclosure;

/**
* @var string
*/
protected $lineBreak;

/**
* @param string $delimiter
* @throws InvalidArgumentException
*/
protected function setDelimiter($delimiter)
{
$this->validateDelimiter($delimiter);
$this->delimiter = $delimiter;
}

/**
* @param string $delimiter
* @throws InvalidArgumentException
*/
protected function validateDelimiter($delimiter)
{
if (strlen($delimiter) > 1) {
throw new InvalidArgumentException(
"Delimiter must be a single character. \"$delimiter\" received",
Exception::INVALID_PARAM,
null,
Exception::INVALID_PARAM_STR
);
}

if (strlen($delimiter) == 0) {
throw new InvalidArgumentException(
"Delimiter cannot be empty.",
Exception::INVALID_PARAM,
null,
Exception::INVALID_PARAM_STR
);
}
}

/**
* @param string $enclosure
* @return $this
* @throws InvalidArgumentException
*/
protected function setEnclosure($enclosure)
{
$this->validateEnclosure($enclosure);
$this->enclosure = $enclosure;
return $this;
}

/**
* @param string $enclosure
* @throws InvalidArgumentException
*/
protected function validateEnclosure($enclosure)
{
if (strlen($enclosure) > 1) {
throw new InvalidArgumentException(
"Enclosure must be a single character. \"$enclosure\" received",
Exception::INVALID_PARAM,
null,
Exception::INVALID_PARAM_STR
);
}
}

/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}

/**
* @return string
*/
public function getEnclosure()
{
return $this->enclosure;
}
}
188 changes: 15 additions & 173 deletions src/CsvFile.php → src/CsvReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,10 @@

namespace Keboola\Csv;

class CsvFile extends \SplFileInfo implements \Iterator
class CsvReader extends AbstractCsvFile implements \Iterator
{
const DEFAULT_DELIMITER = ',';
const DEFAULT_ENCLOSURE = '"';
const DEFAULT_ESCAPED_BY = "";

/**
* @var string
*/
protected $delimiter;

/**
* @var string
*/
protected $enclosure;

/**
* @var string
*/
Expand All @@ -44,9 +32,9 @@ class CsvFile extends \SplFileInfo implements \Iterator
protected $currentRow;

/**
* @var string
* @var array
*/
protected $lineBreak;
protected $header;

/**
* CsvFile constructor.
Expand All @@ -70,6 +58,7 @@ public function __construct(
$this->setDelimiter($delimiter);
$this->setEnclosure($enclosure);
$this->setSkipLines($skipLines);
$this->openCsvFile();
}

public function __destruct()
Expand All @@ -79,7 +68,7 @@ public function __destruct()

/**
* @param integer $skipLines
* @return CsvFile
* @return CsvReader
* @throws InvalidArgumentException
*/
protected function setSkipLines($skipLines)
Expand All @@ -105,71 +94,6 @@ protected function validateSkipLines($skipLines)
}
}

/**
* @param string $delimiter
* @return CsvFile
* @throws InvalidArgumentException
*/
protected function setDelimiter($delimiter)
{
$this->validateDelimiter($delimiter);
$this->delimiter = $delimiter;
return $this;
}

/**
* @param string $delimiter
* @throws InvalidArgumentException
*/
protected function validateDelimiter($delimiter)
{
if (strlen($delimiter) > 1) {
throw new InvalidArgumentException(
"Delimiter must be a single character. \"$delimiter\" received",
Exception::INVALID_PARAM,
null,
Exception::INVALID_PARAM_STR
);
}

if (strlen($delimiter) == 0) {
throw new InvalidArgumentException(
"Delimiter cannot be empty.",
Exception::INVALID_PARAM,
null,
Exception::INVALID_PARAM_STR
);
}
}

/**
* @param string $enclosure
* @return $this
* @throws InvalidArgumentException
*/
protected function setEnclosure($enclosure)
{
$this->validateEnclosure($enclosure);
$this->enclosure = $enclosure;
return $this;
}

/**
* @param string $enclosure
* @throws InvalidArgumentException
*/
protected function validateEnclosure($enclosure)
{
if (strlen($enclosure) > 1) {
throw new InvalidArgumentException(
"Enclosure must be a single character. \"$enclosure\" received",
Exception::INVALID_PARAM,
null,
Exception::INVALID_PARAM_STR
);
}
}

/**
* @return string
* @throws Exception
Expand All @@ -178,7 +102,6 @@ protected function detectLineBreak()
{
rewind($this->getFilePointer());
$sample = fread($this->getFilePointer(), 10000);
rewind($this->getFilePointer());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nespoléhal bych na to, že to nikdo nezavolá jen proto, že je to protected. Ta třída jde extendovat a budou z toho těžko odhalitelné chyby. Nebo co ji dát final?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tomu nerozumim, vadi ti, ze ma detectLineBreak side effect? no a co? nejmenuje se to getLineBreak (srovnej se soucasnou verzi, kde getLineBreak ma sideeffect), stejne tak ma openCsvfile a closeCsvFile maji side effect, pokud si tu tridu nekdo extenduje a zavola to nekde uprostred cteni/zapisu, tak je proste blbej

nevidim smysl v tom snazit se tomu nejak zabranit

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


$possibleLineBreaks = [
"\r\n", // win
Expand Down Expand Up @@ -218,33 +141,27 @@ protected function readLine()
}

/**
* @param string $mode
* @return resource
* @throws Exception
*/
protected function getFilePointer($mode = 'r')
protected function getFilePointer()
{
if (!is_resource($this->filePointer)) {
$this->openCsvFile($mode);
}
return $this->filePointer;
}

/**
* @param string $mode
* @throws Exception
*/
protected function openCsvFile($mode)
protected function openCsvFile()
{
if ($mode == 'r' && !is_file($this->getPathname())) {
if (!is_file($this->getPathname())) {
throw new Exception(
"Cannot open file {$this->getPathname()}",
Exception::FILE_NOT_EXISTS,
null,
Exception::FILE_NOT_EXISTS_STR
);
}
$this->filePointer = @fopen($this->getPathname(), $mode);
$this->filePointer = @fopen($this->getPathname(), "r");
if (!$this->filePointer) {
throw new Exception(
"Cannot open file {$this->getPathname()} " . error_get_last()['message'],
Expand All @@ -253,6 +170,10 @@ protected function openCsvFile($mode)
Exception::FILE_NOT_EXISTS_STR
);
}
$this->lineBreak = $this->detectLineBreak();
rewind($this->getFilePointer());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proč je tady jiný rewind než na L176?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tj. proč tady není $this->rewind()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protoze to dela neco jinyho - header je vzdycky prvni radek, ale $this->rewind respektuje skip lines, kdyby se tady volalo $this->rewind, tak bude header nejaka blbost

$this->header = $this->readLine();
$this->rewind();
}

protected function closeFile()
Expand All @@ -262,22 +183,6 @@ protected function closeFile()
}
}

/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}

/**
* @return string
*/
public function getEnclosure()
{
return $this->enclosure;
}

/**
* @return string
*/
Expand All @@ -299,81 +204,18 @@ public function getColumnsCount()
*/
public function getHeader()
{
$this->rewind();
$current = $this->current();
if (is_array($current)) {
return $current;
if ($this->header) {
return $this->header;
}

return [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kdy to propadne sem? IMHO nikdy, ne?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u prazdnyho souboru

}

/**
* @param array $row
* @throws Exception
*/
public function writeRow(array $row)
{
$str = $this->rowToStr($row);
$ret = fwrite($this->getFilePointer('w+'), $str);

/* According to http://php.net/fwrite the fwrite() function
should return false on error. However not writing the full
string (which may occur e.g. when disk is full) is not considered
as an error. Therefore both conditions are necessary. */
if (($ret === false) || (($ret === 0) && (strlen($str) > 0))) {
throw new Exception(
"Cannot write to file {$this->getPathname()}",
Exception::WRITE_ERROR,
null,
Exception::WRITE_ERROR_STR
);
}
}

/**
* @param array $row
* @return string
* @throws Exception
*/
public function rowToStr(array $row)
{
$return = [];
foreach ($row as $column) {
if (!(
is_scalar($column)
|| is_null($column)
|| (
is_object($column)
&& method_exists($column, '__toString')
)
)) {
$type = gettype($column);
throw new Exception(
"Cannot write {$type} into a column",
Exception::WRITE_ERROR,
null,
Exception::WRITE_ERROR_STR,
['column' => $column]
);
}

$return[] = $this->getEnclosure() .
str_replace($this->getEnclosure(), str_repeat($this->getEnclosure(), 2), $column) .
$this->getEnclosure();
}
return implode($this->getDelimiter(), $return) . "\n";
}

/**
* @return string
* @throws Exception
*/
public function getLineBreak()
{
if (!$this->lineBreak) {
$this->lineBreak = $this->detectLineBreak();
}
return $this->lineBreak;
}

Expand Down
Loading