replace CSV plugin

This commit is contained in:
Li Jianxuan 2015-01-28 03:48:30 +00:00
parent d323ccd1ff
commit 6eae29d926
56 changed files with 2666 additions and 2025 deletions

View File

@ -0,0 +1,10 @@
<?php
namespace Goodby\CSV\Export\Protocol\Exception;
/**
* Throws if it is unable to write CSV file
*/
class IOException extends \RuntimeException
{
}

View File

@ -0,0 +1,22 @@
<?php
namespace Goodby\CSV\Export\Protocol;
use Traversable;
use Goodby\CSV\Export\Protocol\Exception\IOException;
/**
* Interface of the Exporter
*/
interface ExporterInterface
{
/**
* Export data as CSV file
*
* @param string $filename
* @param array|Traversable $rows
* @throws IOException
* @return void
*/
public function export($filename, $rows);
}

View File

@ -0,0 +1,88 @@
<?php
namespace Goodby\CSV\Export\Standard\Collection;
use Iterator;
use IteratorAggregate;
class CallbackCollection implements Iterator
{
private $callable;
private $data;
public function __construct($data, $callable)
{
$this->callable = $callable;
if (!is_callable($callable)) {
throw new \InvalidArgumentException('the second argument must be callable');
}
if (is_array($data)) {
$ao = new \ArrayObject($data);
$this->data = $ao->getIterator();
} elseif ($data instanceof Iterator) {
$this->data = $data;
} elseif ($data instanceof IteratorAggregate) {
$this->data = $data->getIterator();
} else {
throw new \InvalidArgumentException('data must be an array or an Iterator/IteratorAggregate');
}
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
*/
public function current()
{
return call_user_func($this->callable, $this->data->current());
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
*/
public function next()
{
$this->data->next();
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->data->key();
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
return $this->data->valid();
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->data->rewind();
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Goodby\CSV\Export\Standard\Collection;
use Iterator;
use PDO;
class PdoCollection implements Iterator
{
/**
* @var \PDOStatement
*/
private $stmt;
private $rowCount;
private $current = 0;
public function __construct(\PDOStatement $stmt)
{
$this->stmt = $stmt;
$this->rowCount = $this->stmt->rowCount();
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
*/
public function current()
{
return $this->stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
*/
public function next()
{
$this->current++;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
$this->current;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
return ($this->rowCount > $this->current);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->stmt->execute();
$this->current = 0;
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace Goodby\CSV\Export\Standard;
use SplFileObject;
class CsvFileObject extends SplFileObject
{
const FILE_MODE_WRITE = 'w';
const FILE_MODE_APPEND = 'a';
/**
* newline character
* @var string
*/
private $newline = "\n";
/**
* CSV filter
* @var callable
*/
private $csvFilter;
/**
* @param string $newline
* @return void
*/
public function setNewline($newline)
{
$this->newline = $newline;
}
/**
* Set csv filter
* @param callable $filter
*/
public function setCsvFilter($filter)
{
$this->csvFilter = $filter;
}
/**
* Write a field array as a CSV line
* @param array $fields
* @param string $delimiter
* @param string $enclosure
* @return int|void
*/
public function fputcsv($fields, $delimiter = null, $enclosure = null)
{
// Temporary output a line to memory to get line as string
$fp = fopen('php://temp', 'w+');
$arguments = func_get_args();
array_unshift($arguments, $fp);
call_user_func_array('fputcsv', $arguments);
rewind($fp);
$line = '';
while ( feof($fp) === false ) {
$line .= fgets($fp);
}
fclose($fp);
/**
* Because the php_fputcsv() implementation in PHP´s source code
* has a hardcoded "\n", this method replaces the last LF code
* with what the client code wishes.
*/
$line = rtrim($line, "\n"). $this->newline;
// if the enclosure was '' | false
if (empty($enclosure)) {
$line = str_replace("\0", '', $line);
}
if ( is_callable($this->csvFilter) ) {
$line = call_user_func($this->csvFilter, $line);
}
return $this->fwrite($line);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Goodby\CSV\Export\Standard\Exception;
class StrictViolationException extends \RuntimeException
{
}

View File

@ -0,0 +1,110 @@
<?php
namespace Goodby\CSV\Export\Standard;
use Goodby\CSV\Export\Protocol\ExporterInterface;
use Goodby\CSV\Export\Protocol\Exception\IOException;
use Goodby\CSV\Export\Standard\Exception\StrictViolationException;
/**
* Standard exporter class
*/
class Exporter implements ExporterInterface
{
/**
* @var ExporterConfig
*/
private $config;
/**
* @var int
*/
private $rowConsistency = null;
/**
* @var bool
*/
private $strict = true;
/**
* Return new Exporter object
* @param ExporterConfig $config
*/
public function __construct(ExporterConfig $config)
{
$this->config = $config;
}
/**
* Disable strict mode
*/
public function unstrict()
{
$this->strict = false;
}
/**
* {@inherit}
* @throws StrictViolationException
*/
public function export($filename, $rows)
{
$delimiter = $this->config->getDelimiter();
$enclosure = $this->config->getEnclosure();
$enclosure = empty($enclosure) ? "\0" : $enclosure;
$newline = $this->config->getNewline();
$fromCharset = $this->config->getFromCharset();
$toCharset = $this->config->getToCharset();
$fileMode = $this->config->getFileMode();
$columnHeaders = $this->config->getColumnHeaders();
try {
$csv = new CsvFileObject($filename, $fileMode);
} catch ( \Exception $e ) {
throw new IOException($e->getMessage(), null, $e);
}
$csv->setNewline($newline);
if ( $toCharset ) {
$csv->setCsvFilter(function($line) use($toCharset, $fromCharset) {
return mb_convert_encoding($line, $toCharset, $fromCharset);
});
}
if (count($columnHeaders) > 0) {
$this->checkRowConsistency($columnHeaders);
$csv->fputcsv($columnHeaders, $delimiter, $enclosure);
}
foreach ( $rows as $row ) {
$this->checkRowConsistency($row);
$csv->fputcsv($row, $delimiter, $enclosure);
}
$csv->fflush();
}
/**
* Check if the column count is consistent with comparing other rows
* @param array|\Countable $row
* @throws Exception\StrictViolationException
*/
private function checkRowConsistency($row)
{
if ( $this->strict === false ) {
return;
}
$current = count($row);
if ( $this->rowConsistency === null ) {
$this->rowConsistency = $current;
}
if ( $current !== $this->rowConsistency ) {
throw new StrictViolationException();
}
$this->rowConsistency = $current;
}
}

View File

@ -0,0 +1,218 @@
<?php
namespace Goodby\CSV\Export\Standard;
/**
* Config for Exporter object
*/
class ExporterConfig
{
/**
* Delimiter
* @var string
*/
private $delimiter = ',';
/**
* Enclosure
* @var string
*/
private $enclosure = '"';
/**
* Escape
* @var string
*/
private $escape = '\\';
/**
* Newline code
* @var string
*/
private $newline = "\r\n";
/**
* From charset
* @var string
*/
private $fromCharset = 'auto';
/**
* To charset
* @var string
*/
private $toCharset = null;
/**
* File mode
* @var string
*/
private $fileMode = CsvFileObject::FILE_MODE_WRITE;
/**
* The column headers.
* @var array
*/
private $columnHeaders = array();
/**
* Set delimiter
* @param string $delimiter
* @return ExporterConfig
*/
public function setDelimiter($delimiter)
{
$this->delimiter = $delimiter;
return $this;
}
/**
* Return delimiter
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* Set enclosure
* @param string $enclosure
* @return ExporterConfig
*/
public function setEnclosure($enclosure)
{
$this->enclosure = $enclosure;
return $this;
}
/**
* Return enclosure
* @return string
*/
public function getEnclosure()
{
return $this->enclosure;
}
/**
* Set escape
* @param string $escape
* @return ExporterConfig
*/
public function setEscape($escape)
{
$this->escape = $escape;
return $this;
}
/**
* Return escape
* @return string
*/
public function getEscape()
{
return $this->escape;
}
/**
* Set newline
* @param string $newline
* @return ExporterConfig
*/
public function setNewline($newline)
{
$this->newline = $newline;
return $this;
}
/**
* Return newline
* @return string
*/
public function getNewline()
{
return $this->newline;
}
/**
* Set from-character set
* @param string $fromCharset
* @return ExporterConfig
*/
public function setFromCharset($fromCharset)
{
$this->fromCharset = $fromCharset;
return $this;
}
/**
* Return from-character set
* @return string
*/
public function getFromCharset()
{
return $this->fromCharset;
}
/**
* Set to-character set
* @param string $toCharset
* @return ExporterConfig
*/
public function setToCharset($toCharset)
{
$this->toCharset = $toCharset;
return $this;
}
/**
* Return to-character set
* @return string
*/
public function getToCharset()
{
return $this->toCharset;
}
/**
* Set file mode
* @param string $fileMode
* @return ExporterConfig
*/
public function setFileMode($fileMode)
{
$this->fileMode = $fileMode;
return $this;
}
/**
* Return file mode
* @return string
*/
public function getFileMode()
{
return $this->fileMode;
}
/**
* Set the column headers.
* @param array $columnHeaders
* @return ExporterConfig
*/
public function setColumnHeaders(array $columnHeaders)
{
$this->columnHeaders = $columnHeaders;
return $this;
}
/**
* Get the column headers.
* @return array
*/
public function getColumnHeaders()
{
return $this->columnHeaders;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Goodby\CSV\Export\Tests\Protocol;
use Goodby\CSV\Export\Protocol\Exception\IOException;
class ExporterInterfaceTest extends \PHPUnit_Framework_TestCase
{
public function testExport()
{
$exporter = $this->getMock('Goodby\CSV\Export\Protocol\ExporterInterface');
$exporter->expects($this->once())->method('export');
$exporter->export('filename', array(
array('ID', 'name', 'email'),
array('1', 'alice', 'alice@example.com'),
array('2', 'bob', 'bob@example.com'),
));
}
/**
* @expectedException \Goodby\CSV\Export\Protocol\Exception\IOException
*/
public function testExportsThrowsIOException()
{
$exporter = $this->getMock('Goodby\CSV\Export\Protocol\ExporterInterface');
$exporter
->expects($this->once())
->method('export')
->will($this->throwException(new IOException('Unable to write')));
$exporter->export('/path/to/file.csv', array(
array('ID', 'name', 'email'),
array('1', 'alice', 'alice@example.com'),
array('2', 'bob', 'bob@example.com'),
));
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Goodby\CSV\Export\Tests\Standard\Join\Collection;
use Goodby\CSV\Export\Standard\Collection\PdoCollection;
use Goodby\CSV\TestHelper\DbManager;
use Goodby\CSV\Export\Standard\Collection\CallbackCollection;
class PdoCollectionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Goodby\CSV\TestHelper\DbManager
*/
private $manager = null;
public function setUp()
{
$this->manager = new DbManager();
$pdo = $this->manager->getPdo();
$stmt = $pdo->prepare("CREATE TABLE collection_test ( id INT, name VARCHAR(32) )");
$stmt->execute();
$pdo->prepare("INSERT INTO collection_test VALUES(1, 'name')")->execute();
$pdo->prepare("INSERT INTO collection_test VALUES(2, 'name')")->execute();
$pdo->prepare("INSERT INTO collection_test VALUES(3, 'name')")->execute();
}
public function tearDown()
{
unset($this->manager);
}
public function testUsage()
{
$pdo = $this->manager->getPdo();
$stmt = $pdo->prepare("SELECT * FROM collection_test");
$stmt->execute();
$pdoCollection = new PdoCollection($stmt);
foreach ($pdoCollection as $line) {
$this->assertEquals("name", $line["name"]);
}
}
public function testUsageWithCallbackCollection()
{
$pdo = $this->manager->getPdo();
$stmt = $pdo->prepare("SELECT * FROM collection_test");
$stmt->execute();
$pdoCollection = new PdoCollection($stmt);
$callbackCollection = new CallbackCollection($pdoCollection, function($row) {
$row['test'] = 'test';
return $row;
});
foreach ($callbackCollection as $line) {
$this->assertEquals('test', $line['test']);
}
}
}

View File

@ -0,0 +1,188 @@
<?php
namespace Goodby\CSV\Export\Tests\Standard\Join;
use Goodby\CSV\Export\Standard\Exporter;
use Goodby\CSV\Export\Standard\ExporterConfig;
use Goodby\CSV\Export\Standard\Exception\StrictViolationException;
use Goodby\CSV\Export\Protocol\Exception\IOException;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStream;
class ExporterTest extends \PHPUnit_Framework_TestCase
{
/**
* @var vfsStreamDirectory
*/
private $root;
/**
* set up test environment
*/
public function setUp()
{
$this->root = vfsStream::setup('output');
}
public function testExport()
{
$config = new ExporterConfig();
$exporter = new Exporter($config);
$this->assertFileNotExists('vfs://output/data.csv');
$exporter->export('vfs://output/data.csv', array(
array('ID', 'name', 'email'),
array('1', 'alice', 'alice@example.com'),
array('2', 'bob', 'bob@example.com'),
));
$this->assertFileExists('vfs://output/data.csv');
$expectedContents = "ID,name,email\r\n";
$expectedContents .= "1,alice,alice@example.com\r\n";
$expectedContents .= "2,bob,bob@example.com\r\n";
$this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv'));
}
public function test_export_with_carriage_return()
{
$config = new ExporterConfig();
$config->setNewline("\r");
$exporter = new Exporter($config);
$exporter->unstrict();
$this->assertFileNotExists('vfs://output/data.csv');
$exporter->export('vfs://output/data.csv', array(
array('aaa', 'bbb', 'ccc', 'dddd'),
array('123', '456', '789'),
array('"aaa"', '"bbb"', '', ''),
));
$this->assertFileExists('vfs://output/data.csv');
$expectedContents = "aaa,bbb,ccc,dddd\r";
$expectedContents .= "123,456,789\r";
$expectedContents .= '"""aaa""","""bbb""",,'."\r";
$this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv'));
}
public function testUnstrict()
{
$config = new ExporterConfig();
$exporter = new Exporter($config);
$this->assertAttributeSame(true, 'strict', $exporter);
$exporter->unstrict();
$this->assertAttributeSame(false, 'strict', $exporter);
}
/**
* @expectedException \Goodby\CSV\Export\Standard\Exception\StrictViolationException
*/
public function testStrict()
{
$config = new ExporterConfig();
$exporter = new Exporter($config);
$exporter->export('vfs://output/data.csv', array(
array('a', 'b', 'c'),
array('a', 'b', 'c'),
array('a', 'b'),
));
}
/**
* @requires PHP 5.4
*/
public function test_throwing_IOException_when_failed_to_write_file()
{
$noWritableCsv = 'vfs://output/no-writable.csv';
touch($noWritableCsv);
chmod($noWritableCsv, 0444);
$this->assertFalse(is_writable($noWritableCsv));
$config = new ExporterConfig();
$exporter = new Exporter($config);
$e = null;
try {
$exporter->export($noWritableCsv, array(
array('a', 'b', 'c'),
));
} catch ( IOException $e ) {
}
$this->assertTrue($e instanceof IOException);
$this->assertContains('failed to open', $e->getMessage());
}
public function test_encoding()
{
$csv = 'vfs://output/euc.csv';
$this->assertFileNotExists($csv);
$config = new ExporterConfig();
$config->setToCharset('EUC-JP');
$config->setNewline("\n");
$exporter = new Exporter($config);
$exporter->export($csv, array(
array('あ', 'い', 'う', 'え', 'お'),
));
$this->assertFileEquals(__DIR__.'/csv_files/euc-jp.csv', $csv);
}
public function test_without_encoding()
{
$csv = 'vfs://output/utf-8.csv';
$this->assertFileNotExists($csv);
$config = new ExporterConfig();
$config->setNewline("\n");
$exporter = new Exporter($config);
$exporter->export($csv, array(
array('✔', '✔', '✔'),
array('★', '★', '★'),
));
$this->assertFileEquals(__DIR__.'/csv_files/utf-8.csv', $csv);
}
public function test_unseekable_wrapper_and_custom_newline_code()
{
$config = new ExporterConfig();
$config->setNewline("\r\n");
$exporter = new Exporter($config);
ob_start();
$exporter->export('php://output', array(
array('a', 'b', 'c'),
array('1', '2', '3'),
));
$output = ob_get_clean();
$expectedCount = "a,b,c\r\n1,2,3\r\n";
$this->assertSame($expectedCount, $output);
}
public function test_multiple_line_columns()
{
$csv = 'vfs://output/multiple-lines.csv';
$this->assertFileNotExists($csv);
$config = new ExporterConfig();
$config->setNewline("\r\n");
$exporter = new Exporter($config);
$exporter->export($csv, array(
array("line1\r\nline2\r\nline3", "single-line"),
array("line1\r\nline2\r\nline3", "single-line"),
array("line1\r\nline2\r\nline3", "single-line"),
));
$this->assertFileEquals(__DIR__.'/csv_files/multiple-lines.csv', $csv);
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace Goodby\CSV\Export\Tests\Standard\Join;
use Goodby\CSV\Export\Standard\Exporter;
use Goodby\CSV\Export\Standard\ExporterConfig;
use Goodby\CSV\Export\Standard\Collection\PdoCollection;
use Goodby\CSV\TestHelper\DbManager;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use Goodby\CSV\Export\Standard\Collection\CallbackCollection;
class UsageTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Goodby\CSV\TestHelper\DbManager
*/
private $manager = null;
/**
* @var vfsStreamDirectory
*/
private $root;
public function setUp()
{
$this->root = vfsStream::setup('output');
$this->manager = new DbManager();
$pdo = $this->manager->getPdo();
$stmt = $pdo->prepare("CREATE TABLE collection_test ( id INT, name VARCHAR(32) )");
$stmt->execute();
$pdo->prepare("INSERT INTO collection_test VALUES(1, 'name')")->execute();
$pdo->prepare("INSERT INTO collection_test VALUES(2, 'name')")->execute();
$pdo->prepare("INSERT INTO collection_test VALUES(3, 'name')")->execute();
}
public function tearDown()
{
unset($this->manager);
}
public function testUsage()
{
$pdo = $this->manager->getPdo();
$stmt = $pdo->prepare("SELECT * FROM collection_test");
$stmt->execute();
$this->assertFileNotExists('vfs://output/data.csv');
$collection = new PdoCollection($stmt);
$config = new ExporterConfig();
$exporter = new Exporter($config);
$exporter->export('vfs://output/data.csv', $collection);
$expectedContents = "1,name\r\n";
$expectedContents .= "2,name\r\n";
$expectedContents .= "3,name\r\n";
$this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv'));
}
public function testUsageWithCallbackCollection()
{
$this->assertFileNotExists('vfs://output/data.csv');
$data = array();
$data[] = array(1, 'name1');
$data[] = array(2, 'name2');
$data[] = array(3, 'name3');
$collection = new CallbackCollection($data, function($row) {
$row[1] = $row[1] . '!';
return $row;
});
$config = new ExporterConfig();
$exporter = new Exporter($config);
$exporter->export('vfs://output/data.csv', $collection);
$expectedContents = "1,name1!\r\n";
$expectedContents .= "2,name2!\r\n";
$expectedContents .= "3,name3!\r\n";
$this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv'));
}
}

View File

@ -0,0 +1 @@
あ,い,う,え,お
1

View File

@ -0,0 +1,9 @@
"line1
line2
line3",single-line
"line1
line2
line3",single-line
"line1
line2
line3",single-line
1 line1 line2 line3 single-line
2 line1 line2 line3 single-line
3 line1 line2 line3 single-line

View File

@ -0,0 +1,2 @@
✔,✔,✔
★,★,★
1
2

View File

@ -0,0 +1,50 @@
<?php
namespace Goodby\CSV\Export\Tests\Standard\Unit\Collection;
use Goodby\CSV\Export\Standard\Collection\CallbackCollection;
use Goodby\CSV\Export\Tests\Standard\Unit\Collection\SampleAggIterator;
class CallbackCollectionTest extends \PHPUnit_Framework_TestCase
{
public function testSample()
{
$data = array();
$data[] = array('user', 'name1');
$data[] = array('user', 'name2');
$data[] = array('user', 'name3');
$collection = new CallbackCollection($data, function($mixed) {
return $mixed;
});
$index = 1;
foreach ($collection as $each) {
$this->assertEquals($each[0], 'user');
$this->assertEquals($each[1], 'name' . $index);
$index++;
}
}
public function testIteratorAggregate()
{
$data = array();
$data[] = array('user', 'name1');
$data[] = array('user', 'name2');
$data[] = array('user', 'name3');
$iterator = new SampleAggIterator($data);
$collection = new CallbackCollection($iterator, function($mixed) {
return $mixed;
});
$index = 1;
foreach ($collection as $each) {
$this->assertEquals($each[0], 'user');
$this->assertEquals($each[1], 'name' . $index);
$index++;
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Goodby\CSV\Export\Tests\Standard\Unit\Collection;
use \ArrayIterator;
class SampleAggIterator implements \IteratorAggregate
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function getIterator()
{
return new ArrayIterator($this->data);
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Goodby\CSV\Export\Tests\Standard\Unit;
use Goodby\CSV\Export\Standard\ExporterConfig;
class ExporterConfigTest extends \PHPUnit_Framework_TestCase
{
public function testDelimiter()
{
$config = new ExporterConfig();
$this->assertSame(',', $config->getDelimiter());
$this->assertSame('del', $config->setDelimiter('del')->getDelimiter());
}
public function testEnclosure()
{
$config = new ExporterConfig();
$this->assertSame('"', $config->getEnclosure());
$this->assertSame('enc', $config->setEnclosure('enc')->getEnclosure());
}
public function testEscape()
{
$config = new ExporterConfig();
$this->assertSame('\\', $config->getEscape());
$this->assertSame('esc', $config->setEscape('esc')->getEscape());
}
public function testNewline()
{
$config = new ExporterConfig();
$this->assertSame("\r\n", $config->getNewline());
$this->assertSame("\r", $config->setNewline("\r")->getNewline());
}
public function testFromCharset()
{
$config = new ExporterConfig();
$this->assertSame('auto', $config->getFromCharset());
$this->assertSame('UTF-8', $config->setFromCharset('UTF-8')->getFromCharset());
}
public function testToCharset()
{
$config = new ExporterConfig();
$this->assertSame(null, $config->getToCharset());
$this->assertSame('UTF-8', $config->setToCharset('UTF-8')->getToCharset());
}
public function testColumnHeaders()
{
$columnHeaders = array(
'Header 1',
'Header 2',
'Header 3',
);
$config = new ExporterConfig();
$this->assertSame(array(), $config->getColumnHeaders());
$this->assertSame($columnHeaders, $config->setColumnHeaders($columnHeaders)->getColumnHeaders());
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Goodby\CSV\Import\Protocol\Exception;
/**
* Throws if csv file not found
*/
class CsvFileNotFoundException extends \RuntimeException
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Goodby\CSV\Import\Protocol\Exception;
/**
* Invalid lexical Exception
*/
class InvalidLexicalException extends \RuntimeException
{
}

View File

@ -0,0 +1,15 @@
<?php
namespace Goodby\CSV\Import\Protocol;
/**
* Interface of the Interpreter
*/
interface InterpreterInterface
{
/**
* @param $line
* @return void
*/
public function interpret($line);
}

View File

@ -0,0 +1,20 @@
<?php
namespace Goodby\CSV\Import\Protocol;
use Goodby\CSV\Import\Protocol\Exception\CsvFileNotFoundException;
/**
* Interface of Lexer
*/
interface LexerInterface
{
/**
* Parse csv file
* @param string $filename
* @param InterpreterInterface $interpreter
* @return boolean
* @throws CsvFileNotFoundException
*/
public function parse($filename, InterpreterInterface $interpreter);
}

View File

@ -0,0 +1,7 @@
<?php
namespace Goodby\CSV\Import\Standard\Exception;
class StrictViolationException extends \RuntimeException
{
}

View File

@ -0,0 +1,120 @@
<?php
namespace Goodby\CSV\Import\Standard;
use Goodby\CSV\Import\Protocol\InterpreterInterface;
use Goodby\CSV\Import\Protocol\Exception\InvalidLexicalException;
use Goodby\CSV\Import\Standard\Exception\StrictViolationException;
/**
* standard interpreter
*/
class Interpreter implements InterpreterInterface
{
/**
* @var array
*/
private $observers = array();
/**
* @var int
*/
private $rowConsistency = null;
/**
* @var bool
*/
private $strict = true;
/**
* interpret line
*
* @param $line
* @return void
* @throws \Goodby\CSV\Import\Protocol\Exception\InvalidLexicalException
*/
public function interpret($line)
{
$this->checkRowConsistency($line);
if (!is_array($line)) {
throw new InvalidLexicalException('line is must be array');
}
$this->notify($line);
}
public function unstrict()
{
$this->strict = false;
}
/**
* add observer
*
* @param callable $observer
*/
public function addObserver($observer)
{
$this->checkCallable($observer);
$this->observers[] = $observer;
}
/**
* notify to observers
*
* @param $line
*/
private function notify($line)
{
$observers = $this->observers;
foreach ($observers as $observer) {
$this->delegate($observer, $line);
}
}
/**
* delegate to observer
*
* @param $observer
* @param $line
*/
private function delegate($observer, $line)
{
call_user_func($observer, $line);
}
/**
* check observer is callable
*
* @param $observer
* @throws \InvalidArgumentException
*/
private function checkCallable($observer)
{
if (!is_callable($observer)) {
throw new \InvalidArgumentException('observer must be callable');
}
}
private function checkRowConsistency($line)
{
if (!$this->strict) {
return;
}
$current = count($line);
if ($this->rowConsistency === null) {
$this->rowConsistency = $current;
}
if ($current !== $this->rowConsistency) {
throw new StrictViolationException(sprintf('Column size should be %u, but %u columns given', $this->rowConsistency, $current));
}
$this->rowConsistency = $current;
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Goodby\CSV\Import\Standard;
use Goodby\CSV\Import\Protocol\LexerInterface;
use Goodby\CSV\Import\Protocol\InterpreterInterface;
use Goodby\CSV\Import\Standard\StreamFilter\ConvertMbstringEncoding;
use SplFileObject;
class Lexer implements LexerInterface
{
/**
* @var LexerConfig
*/
private $config;
/**
* Return new Lexer object
* @param LexerConfig $config
*/
public function __construct(LexerConfig $config = null)
{
if (!$config) {
$config = new LexerConfig();
}
$this->config = $config;
ConvertMbstringEncoding::register();
}
/**
* {@inherit}
*/
public function parse($filename, InterpreterInterface $interpreter)
{
ini_set('auto_detect_line_endings', true); // For mac's office excel csv
$delimiter = $this->config->getDelimiter();
$enclosure = $this->config->getEnclosure();
$escape = $this->config->getEscape();
$fromCharset = $this->config->getFromCharset();
$toCharset = $this->config->getToCharset();
$flags = $this->config->getFlags();
$ignoreHeader = $this->config->getIgnoreHeaderLine();
if ( $fromCharset === null ) {
$url = $filename;
} else {
$url = ConvertMbstringEncoding::getFilterURL($filename, $fromCharset, $toCharset);
}
$csv = new SplFileObject($url);
$csv->setCsvControl($delimiter, $enclosure, $escape);
$csv->setFlags($flags);
$originalLocale = setlocale(LC_ALL, '0'); // Backup current locale
setlocale(LC_ALL, 'en_US.UTF-8');
foreach ( $csv as $lineNumber => $line ) {
if ($ignoreHeader && $lineNumber == 0 || (count($line) === 1 && empty($line[0]))) {
continue;
}
$interpreter->interpret($line);
}
parse_str(str_replace(';', '&', $originalLocale), $locale_array);
setlocale(LC_ALL, $locale_array); // Reset locale
}
}

View File

@ -0,0 +1,185 @@
<?php
namespace Goodby\CSV\Import\Standard;
use SplFileObject;
/**
* Config for Lexer object
*/
class LexerConfig
{
/**
* @var string
*/
private $delimiter = ',';
/**
* @var string
*/
private $enclosure = '"';
/**
* @var string
*/
private $escape = '\\';
/**
* @var string
*/
private $fromCharset;
/**
* @var string
*/
private $toCharset;
/**
* @var integer
*/
private $flags = SplFileObject::READ_CSV;
/**
* @var bool
*/
private $ignoreHeaderLine = false;
/**
* Set delimiter
* @param string $delimiter
* @return LexerConfig
*/
public function setDelimiter($delimiter)
{
$this->delimiter = $delimiter;
return $this;
}
/**
* Return delimiter
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* Set enclosure
* @param string $enclosure
* @return LexerConfig
*/
public function setEnclosure($enclosure)
{
$this->enclosure = $enclosure;
return $this;
}
/**
* Return enclosure
* @return string
*/
public function getEnclosure()
{
return $this->enclosure;
}
/**
* Set escape
* @param string $escape
* @return LexerConfig
*/
public function setEscape($escape)
{
$this->escape = $escape;
return $this;
}
/**
* Return escape
* @return string
*/
public function getEscape()
{
return $this->escape;
}
/**
* Set from-character set
* @param string $fromCharset
* @return LexerConfig
*/
public function setFromCharset($fromCharset)
{
$this->fromCharset = $fromCharset;
return $this;
}
/**
* Return from-character set
* @return string
*/
public function getFromCharset()
{
return $this->fromCharset;
}
/**
* Set to-character set
* @param string $toCharset
* @return LexerConfig
*/
public function setToCharset($toCharset)
{
$this->toCharset = $toCharset;
return $this;
}
/**
* Return to-character set
* @return string
*/
public function getToCharset()
{
return $this->toCharset;
}
/**
* Set flags
* @param integer $flags Bit mask of the flags to set. See SplFileObject constants for the available flags.
* @return LexerConfig
* @see http://php.net/manual/en/class.splfileobject.php#splfileobject.constants
*/
public function setFlags($flags)
{
$this->flags = $flags;
return $this;
}
/**
* Return flags
* @return integer
*/
public function getFlags()
{
return $this->flags;
}
/**
* @param $ignoreHeaderLine
* @return $this
*/
public function setIgnoreHeaderLine($ignoreHeaderLine)
{
$this->ignoreHeaderLine = $ignoreHeaderLine;
return $this;
}
/**
* @return boolean
*/
public function getIgnoreHeaderLine()
{
return $this->ignoreHeaderLine;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Goodby\CSV\Import\Standard\Observer;
class PdoObserver
{
private $table;
private $columns;
private $dsn;
private $options = null;
private $pdo = null;
public function __construct($table, $columns, $dsn, $options)
{
$this->table = $table;
$this->columns = $columns;
$this->dsn = $dsn;
$this->options = $options;
}
public function notify($line)
{
if ($this->pdo === null) {
$this->pdo = new \PDO($this->dsn, $this->options['user'], $this->options['password']);
}
$this->execute($line);
}
private function execute($line)
{
$line = array_map(function($value) {
$number = filter_var($value, FILTER_VALIDATE_INT);
if ($number !== false) {
return $number;
}
if (is_string($value)) {
if (strtolower($value) === 'null') {
return 'NULL';
}
if (strtolower($value) === 'true') {
return 1;
}
if (strtolower($value) === 'false') {
return 0;
}
return $value;
}
throw new \InvalidArgumentException('value is invalid: ' . var_export($value, 1));
}, $line);
$prepare = array_map(function() {
return '?';
}, $line);
$sql = 'INSERT INTO ' . $this->table . '(' . join(', ', $this->columns) . ')' .
' VALUES(' . join(',', $prepare) . ')';
$stmt = $this->pdo->prepare($sql);
$stmt->execute($line);
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Goodby\CSV\Import\Standard\Observer;
class SqlObserver
{
private $table;
private $columns;
private $path;
private $file = null;
public function __construct($table, $columns, $path)
{
$this->table = $table;
$this->columns = $columns;
$this->path = $path;
}
public function notify($line)
{
$sql = $this->buildSql($line);
if ($this->file === null) {
$this->file = new \SplFileObject($this->path, 'a');
}
$this->file->fwrite($sql);
}
private function buildSql($line)
{
$line = array_map(function($value) {
$number = filter_var($value, FILTER_VALIDATE_INT);
if ($number !== false) {
return $number;
}
if (is_string($value)) {
if (strtolower($value) === 'null') {
return 'NULL';
}
if (strtolower($value) === 'true') {
return 'true';
}
if (strtolower($value) === 'false') {
return 'false';
}
return '"' . addslashes($value) . '"';
}
throw new \InvalidArgumentException('value is invalid: ' . var_export($value, 1));
}, $line);
return 'INSERT INTO ' . $this->table . '(' . join(', ', $this->columns) . ')' .
' VALUES(' . join(', ', $line) . ');';
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Goodby\CSV\Import\Standard\StreamFilter;
use php_user_filter;
use RuntimeException;
class ConvertMbstringEncoding extends php_user_filter
{
/**
* @var string
*/
const FILTER_NAMESPACE = 'convert.mbstring.encoding.';
/**
* @var bool
*/
private static $hasBeenRegistered = false;
/**
* @var string
*/
private $fromCharset;
/**
* @var string
*/
private $toCharset;
/**
* Return filter name
* @return string
*/
public static function getFilterName()
{
return self::FILTER_NAMESPACE.'*';
}
/**
* Register this class as a stream filter
* @throws \RuntimeException
*/
public static function register()
{
if ( self::$hasBeenRegistered === true ) {
return;
}
if ( stream_filter_register(self::getFilterName(), __CLASS__) === false ) {
throw new RuntimeException('Failed to register stream filter: '.self::getFilterName());
}
self::$hasBeenRegistered = true;
}
/**
* Return filter URL
* @param string $filename
* @param string $fromCharset
* @param string $toCharset
* @return string
*/
public static function getFilterURL($filename, $fromCharset, $toCharset = null)
{
if ( $toCharset === null ) {
return sprintf('php://filter/convert.mbstring.encoding.%s/resource=%s', $fromCharset, $filename);
} else {
return sprintf('php://filter/convert.mbstring.encoding.%s:%s/resource=%s', $fromCharset, $toCharset, $filename);
}
}
/**
* @return bool
*/
public function onCreate()
{
if ( strpos($this->filtername, self::FILTER_NAMESPACE) !== 0 ) {
return false;
}
$parameterString = substr($this->filtername, strlen(self::FILTER_NAMESPACE));
if ( ! preg_match('/^(?P<from>[-\w]+)(:(?P<to>[-\w]+))?$/', $parameterString, $matches) ) {
return false;
}
$this->fromCharset = isset($matches['from']) ? $matches['from'] : 'auto';
$this->toCharset = isset($matches['to']) ? $matches['to'] : mb_internal_encoding();
return true;
}
/**
* @param string $in
* @param string $out
* @param string $consumed
* @param $closing
* @return int
*/
public function filter($in, $out, &$consumed, $closing)
{
while ( $bucket = stream_bucket_make_writeable($in) ) {
$bucket->data = mb_convert_encoding($bucket->data, $this->toCharset, $this->fromCharset);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Goodby\CSV\Import\Tests\Protocol;
use Goodby\CSV\Import\Protocol\Exception\InvalidLexicalException;
/**
* unit test for Interface of the Interpreter
*/
class InterpreterTest extends \PHPUnit_Framework_TestCase
{
public function testInterpreterInterface()
{
$line = array();
$interpreter = $this->getMock('\Goodby\CSV\Import\Protocol\InterpreterInterface');
$interpreter->expects($this->once())
->method('interpret')
->with($this->identicalTo($line))
;
$interpreter->interpret($line);
}
/**
* @expectedException \Goodby\CSV\Import\Protocol\Exception\InvalidLexicalException
*/
public function testInterpreterInterfaceWillThrownInvalidLexicalException()
{
$interpreter = $this->getMock('\Goodby\CSV\Import\Protocol\InterpreterInterface');
$interpreter->expects($this->once())
->method('interpret')
->will($this->throwException(new InvalidLexicalException()))
;
$line = "INVALID LEXICAL";
$interpreter->interpret($line);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Goodby\CSV\Import\Tests\Protocol;
use Mockery as m;
/**
* unit test for CSV Lexer
*/
class LexerTest extends \PHPUnit_Framework_TestCase
{
public function testInterface()
{
$lexer = m::mock('\Goodby\CSV\Import\Protocol\LexerInterface');
$interpreter = m::mock('\Goodby\CSV\Import\Protocol\InterpreterInterface');
$path = 'dummy.csv';
$lexer->shouldReceive('parse')->with($path, $interpreter);
$lexer->parse($path, $interpreter);
}
/**
* @expectedException \Goodby\CSV\Import\Protocol\Exception\CsvFileNotFoundException
*/
public function testCsvFileNotFound()
{
$lexer = m::mock('\Goodby\CSV\Import\Protocol\LexerInterface');
$interpreter = m::mock('\Goodby\CSV\Import\Protocol\InterpreterInterface');
$path = 'invalid_dummy.csv';
$lexer->shouldReceive('parse')
->with($path, $interpreter)
->andThrow('Goodby\CSV\Import\Protocol\Exception\CsvFileNotFoundException')
;
$lexer->parse($path, $interpreter);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard\Join;
class CSVFiles
{
public static function getShiftJisCsv()
{
return __DIR__.'/csv_files/sjis.csv';
}
public static function getMacExcelCsv()
{
return __DIR__.'/csv_files/mac-excel.csv';
}
public static function getMacExcelLines()
{
return array(
array('a', 'b', 'c'),
array('d', 'e', 'f'),
);
}
public static function getTabSeparatedCsv()
{
return __DIR__.'/csv_files/tab-separated.csv';
}
public static function getTabSeparatedLines()
{
return array(
array('value1', 'value2', 'value3'),
array('value4', 'value5', 'value6'),
);
}
public static function getColonSeparatedCsv()
{
return __DIR__.'/csv_files/colon-separated.csv';
}
public static function getColonSeparatedLines()
{
return array(
array('value1', 'value2', 'value3'),
array('value4', 'value5', 'value6'),
);
}
public static function getUtf8Csv()
{
return __DIR__.'/csv_files/utf-8.csv';
}
public static function getUtf8Lines()
{
return array(
array('✔', '✔', '✔', '✔'),
array('★', '★', '★', '★'),
array('유', '니', '코', '드'),
);
}
public static function getIssue5CSV()
{
return __DIR__.'/csv_files/issue-5.csv';
}
}

View File

@ -0,0 +1,156 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard\Join;
use Mockery as m;
use Goodby\CSV\Import\Standard\Lexer;
use Goodby\CSV\Import\Standard\Interpreter;
use Goodby\CSV\Import\Standard\LexerConfig;
class LexerTest extends \PHPUnit_Framework_TestCase
{
public function test_shift_jis_CSV()
{
$shiftJisCsv = CSVFiles::getShiftJisCsv();
$lines = array(
array('あ', 'い', 'う', 'え', 'お'),
array('日本語', '日本語', '日本語', '日本語', '日本語'),
array('ぱ', 'ぴ', 'ぷ', 'ぺ', 'ぽ'),
array('"quoted"', "a'quote'", 'a, b and c', '', ''),
);
$interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret'));
$interpreter->expects($this->at(0))->method('interpret')->with($lines[0]);
$interpreter->expects($this->at(1))->method('interpret')->with($lines[1]);
$interpreter->expects($this->at(2))->method('interpret')->with($lines[2]);
$interpreter->expects($this->at(3))->method('interpret')->with($lines[3]);
$config = new LexerConfig();
$config->setToCharset('UTF-8')->setFromCharset('SJIS-win');
$lexer = new Lexer($config);
$lexer->parse($shiftJisCsv, $interpreter);
}
public function test_mac_excel_csv()
{
$csv = CSVFiles::getMacExcelCsv();
$lines = CSVFiles::getMacExcelLines();
$interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret'));
$interpreter->expects($this->at(0))->method('interpret')->with($lines[0]);
$interpreter->expects($this->at(1))->method('interpret')->with($lines[1]);
$config = new LexerConfig();
$lexer = new Lexer($config);
$lexer->parse($csv, $interpreter);
}
public function test_tab_separated_csv()
{
$csv = CSVFiles::getTabSeparatedCsv();
$lines = CSVFiles::getTabSeparatedLines();
$interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret'));
$interpreter->expects($this->at(0))->method('interpret')->with($lines[0]);
$interpreter->expects($this->at(1))->method('interpret')->with($lines[1]);
$config = new LexerConfig();
$config->setDelimiter("\t");
$lexer = new Lexer($config);
$lexer->parse($csv, $interpreter);
}
public function test_colon_separated_csv()
{
$csv = CSVFiles::getColonSeparatedCsv();
$lines = CSVFiles::getColonSeparatedLines();
$interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret'));
$interpreter->expects($this->at(0))->method('interpret')->with($lines[0]);
$interpreter->expects($this->at(1))->method('interpret')->with($lines[1]);
$config = new LexerConfig();
$config->setDelimiter(':');
$lexer = new Lexer($config);
$lexer->parse($csv, $interpreter);
}
public function test_utf8_csv()
{
$csv = CSVFiles::getUtf8Csv();
$lines = CSVFiles::getUtf8Lines();
$interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret'));
$interpreter->expects($this->at(0))->method('interpret')->with($lines[0]);
$interpreter->expects($this->at(1))->method('interpret')->with($lines[1]);
$config = new LexerConfig();
$lexer = new Lexer($config);
$lexer->parse($csv, $interpreter);
}
/**
* When import CSV file with data in Japanese (2 bytes character),
* data imported to database with error encoding
* @link https://github.com/goodby/csv/issues/5
*/
public function test_issue_5()
{
$csvFilename = CSVFiles::getIssue5CSV();
$csvContents = array();
$config = new LexerConfig();
$config
->setToCharset('UTF-8')
->setFromCharset('UTF-8');
$lexer = new Lexer($config);
$interpreter = new Interpreter();
$interpreter->addObserver(function(array $columns) use (&$csvContents) {
$csvContents[] = $columns;
});
$lexer->parse($csvFilename, $interpreter);
$this->assertSame(array(
array("ID", "NAME", "MAKER"),
array("1", "スティック型クリーナ", "alice_updated@example.com"),
array("2", "bob", "bob@example.com"),
array("14", "スティック型クリーナ", "tho@eample.com"),
array("16", "スティック型", "carot@eample.com"),
), $csvContents);
}
public function test_ignore_header()
{
$csvFilename = CSVFiles::getIssue5CSV();
$config = new LexerConfig();
$config
->setIgnoreHeaderLine(true)
->setToCharset('UTF-8')
->setFromCharset('UTF-8');
$lexer = new Lexer($config);
$interpreter = new Interpreter();
$interpreter->addObserver(function(array $columns) use (&$csvContents) {
$csvContents[] = $columns;
});
$lexer->parse($csvFilename, $interpreter);
$this->assertSame(array(
array("1", "スティック型クリーナ", "alice_updated@example.com"),
array("2", "bob", "bob@example.com"),
array("14", "スティック型クリーナ", "tho@eample.com"),
array("16", "スティック型", "carot@eample.com"),
), $csvContents);
}
public function test_instantiation_without_config()
{
$lexer = new Lexer();
$this->assertInstanceOf('Goodby\CSV\Import\Standard\Lexer', $lexer);
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard\Join\Observer;
use Mockery as m;
use Goodby\CSV\Import\Standard\Interpreter;
use Goodby\CSV\Import\Standard\Observer\PdoObserver;
use Goodby\CSV\TestHelper\DbManager;
/**
* unit test for pdo observer
*/
class PdoObserverTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Goodby\CSV\TestHelper\DbManager
*/
private $manager = null;
public function setUp()
{
$this->manager = new DbManager();
$pdo = $this->manager->getPdo();
$stmt = $pdo->prepare("CREATE TABLE test (id INT, name VARCHAR(32), age INT, flag TINYINT, flag2 TINYINT, status VARCHAR(32), contents TEXT)");
$stmt->execute();
}
public function tearDown()
{
unset($this->manager);
}
public function testUsage()
{
$interpreter = new Interpreter();
$table = 'test';
$dsn = $this->manager->getDsn();
$options = array('user' => $this->manager->getUser(), 'password' => $this->manager->getPassword());
$sqlObserver = new PdoObserver($table, array('id', 'name', 'age', 'flag', 'flag2', 'status', 'contents'), $dsn, $options);
$interpreter->addObserver(array($sqlObserver, 'notify'));
$interpreter->interpret(array('123', 'test', '28', 'true', 'false', 'null', 'test"test'));
$pdo = $this->manager->getPdo();
$stmt = $pdo->prepare("SELECT * FROM " . $table);
$stmt->execute();
$result = $stmt->fetch();
$this->assertEquals(123, $result[0]);
$this->assertEquals('test', $result[1]);
$this->assertEquals(28, $result[2]);
$this->assertEquals(1, $result[3]);
$this->assertEquals(0, $result[4]);
$this->assertEquals('NULL', $result[5]);
$this->assertEquals('test"test', $result[6]);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage value is invalid: array
*/
public function testInvalidLine()
{
$interpreter = new Interpreter();
$table = 'test';
$options = array('user' => $this->manager->getUser(), 'password' => $this->manager->getPassword());
$sqlObserver = new PdoObserver($table, array('id', 'name'), $this->manager->getDsn(), $options);
$interpreter->addObserver(array($sqlObserver, 'notify'));
$interpreter->interpret(array('123', array('test', 'test')));
}
}

View File

@ -0,0 +1,2 @@
value1:value2:value3
value4:value5:value6
1 value1:value2:value3
2 value4:value5:value6

View File

@ -0,0 +1,5 @@
ID,NAME,MAKER
1,スティック型クリーナ,alice_updated@example.com
2,bob,bob@example.com
14,スティック型クリーナ,tho@eample.com
16,スティック型,carot@eample.com
1 ID NAME MAKER
2 1 スティック型クリーナ alice_updated@example.com
3 2 bob bob@example.com
4 14 スティック型クリーナ tho@eample.com
5 16 スティック型 carot@eample.com

View File

@ -0,0 +1 @@
a,b,c d,e,f
1 a b c d e f

View File

@ -0,0 +1,4 @@
あ,い,う,え,お
日本語,日本語,日本語,日本語,日本語
ぱ,ぴ,ぷ,ぺ,ぽ
"""quoted""",a'quote',"a, b and c",,
1
2 日本語 日本語 日本語 日本語 日本語
3
4 "quoted" a'quote' a, b and c

View File

@ -0,0 +1,7 @@
value1 value2 value3
value4 value5 value6
1 value1 value2 value3
2 value4 value5 value6

View File

@ -0,0 +1,3 @@
✔,✔,✔,✔
★,★,★,★
유,니,코,드
1
2
3

View File

@ -0,0 +1,45 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard;
/**
* Managing sandbox directory to use when joining with the file system for tests
*/
class SandboxDirectoryManager
{
/**
* Return sandbox directory
* @return string
*/
public static function getSandboxDirectory()
{
return sys_get_temp_dir().'/goodby/csv';
}
/**
* Reset sandbox directory
*/
public static function resetSandboxDirectory()
{
$sandboxDir = self::getSandboxDirectory();
if ( file_exists($sandboxDir) ) {
exec(sprintf('rm -rf %s', $sandboxDir));
}
if ( file_exists($sandboxDir) ) {
throw new \RuntimeException(
sprintf('Cannot continue test: sandbox directory already exists: %s', $sandboxDir)
);
}
mkdir($sandboxDir, 0777, true);
if ( file_exists($sandboxDir) === false ) {
throw new \RuntimeException(
sprintf('Cannot continue test: sandbox directory does not exist: %s', $sandboxDir)
);
}
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard\Unit;
use Mockery as m;
use Goodby\CSV\Import\Standard\Interpreter;
/**
* unit test for Standard Implementation of the Interpreter
*
*/
class InterpreterTest extends \PHPUnit_Framework_TestCase
{
private $expectedLine;
public function setUp()
{
$this->expectedLine = null;
}
/**
* @requires PHP 5.4
*/
public function testStandardInterpreterWithClosure()
{
$this->expectedLine = array('test', 'test', 'test');
$interpreter = new Interpreter();
$interpreter->addObserver(function($line) {
$this->assertEquals($this->expectedLine, $line);
});
$interpreter->interpret($this->expectedLine);
}
public function testStandardInterpreterWithObject()
{
$this->expectedLine = array('test', 'test', 'test');
$object = m::mock('stdClass');
$object->shouldReceive('callback')->with($this->expectedLine)->once();
$interpreter = new Interpreter();
$interpreter->addObserver(array($object, 'callback'));
$interpreter->interpret($this->expectedLine);
}
/**
* @expectedException \Goodby\CSV\Import\Standard\Exception\StrictViolationException
*/
public function testInconsistentColumns()
{
$lines[] = array('test', 'test', 'test');
$lines[] = array('test', 'test');
$interpreter = new Interpreter();
foreach ($lines as $line) {
$interpreter->interpret($line);
}
}
/**
* @expectedException \Goodby\CSV\Import\Standard\Exception\StrictViolationException
*/
public function testInconsistentColumnsLowToHigh()
{
$lines[] = array('test', 'test');
$lines[] = array('test', 'test', 'test');
$interpreter = new Interpreter();
foreach ($lines as $line) {
$interpreter->interpret($line);
}
}
public function testConsistentColumns()
{
$lines[] = array('test', 'test', 'test');
$lines[] = array('test', 'test', 'test');
$interpreter = new Interpreter();
foreach ($lines as $line) {
$interpreter->interpret($line);
}
}
/**
* use un-strict won't throw exception with inconsistent columns
*
*/
public function testInconsistentColumnsWithUnStrict()
{
$lines[] = array('test', 'test', 'test');
$lines[] = array('test', 'test');
$interpreter = new Interpreter();
$interpreter->unstrict();
foreach ($lines as $line) {
$interpreter->interpret($line);
}
}
/**
* @expectedException \Goodby\CSV\Import\Protocol\Exception\InvalidLexicalException
*/
public function testStandardInterpreterWithInvalidLexical()
{
$this->expectedLine = '';
$interpreter = new Interpreter();
$interpreter->interpret($this->expectedLine);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testInvalidCallable()
{
$interpreter = new Interpreter();
$interpreter->addObserver('dummy');
$interpreter->interpret($this->expectedLine);
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard\Unit;
use SplFileObject;
use Goodby\CSV\Import\Standard\LexerConfig;
class LexerConfigTest extends \PHPUnit_Framework_TestCase
{
public function testDelimiter()
{
$config = new LexerConfig();
$this->assertSame(',', $config->getDelimiter());
$config->setDelimiter('del');
$this->assertSame('del', $config->getDelimiter());
}
public function testEnclosure()
{
$config = new LexerConfig();
$this->assertSame('"', $config->getEnclosure());
$this->assertSame('enc', $config->setEnclosure('enc')->getEnclosure());
}
public function testEscape()
{
$config = new LexerConfig();
$this->assertSame('\\', $config->getEscape());
$this->assertSame('esc', $config->setEscape('esc')->getEscape());
}
public function testFromCharset()
{
$config = new LexerConfig();
$this->assertSame(null, $config->getFromCharset());
$this->assertSame('UTF-8', $config->setFromCharset('UTF-8')->getFromCharset());
}
public function testToCharset()
{
$config = new LexerConfig();
$this->assertSame(null, $config->getToCharset());
$this->assertSame('UTF-8', $config->setToCharset('UTF-8')->getToCharset());
}
public function testFlags()
{
$config = new LexerConfig();
$this->assertSame(SplFileObject::READ_CSV, $config->getFlags());
$config->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::READ_CSV);
$flags = (SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::READ_CSV);
$this->assertSame($flags, $config->getFlags());
}
public function testIgnoreHeaderLine()
{
$config = new LexerConfig();
$this->assertSame(false, $config->getIgnoreHeaderLine());
$this->assertSame(true, $config->setIgnoreHeaderLine(true)->getIgnoreHeaderLine());
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard\Unit\Observer;
use Mockery as m;
use Goodby\CSV\Import\Standard\Interpreter;
use Goodby\CSV\Import\Standard\Observer\SqlObserver;
/**
* unit test for sql observer
*/
class SqlObserverTest extends \PHPUnit_Framework_TestCase
{
public function testUsage()
{
$interpreter = new Interpreter();
$tempDir = sys_get_temp_dir();
$path = $tempDir . DIRECTORY_SEPARATOR . 'test.sql';
if (file_exists($path)) {
unlink($path);
}
$sqlObserver = new SqlObserver('test', array('id', 'name', 'age', 'flag', 'flag2', 'status', 'contents'), $path);
$interpreter->addObserver(array($sqlObserver, 'notify'));
$interpreter->interpret(array('123', 'test', '28', 'true', 'false', 'null', 'test"test'));
$expectedSql = 'INSERT INTO test(id, name, age, flag, flag2, status, contents) VALUES(123, "test", 28, true, false, NULL, "test\"test");';
$this->assertEquals($expectedSql, file_get_contents($path));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testInvalidLine()
{
$interpreter = new Interpreter();
$sqlObserver = new SqlObserver('test', array('id', 'name'), 'dummy');
$interpreter->addObserver(array($sqlObserver, 'notify'));
$interpreter->interpret(array('123', array('test')));
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace Goodby\CSV\Import\Tests\Standard\Unit\StreamFilter;
use Expose\Expose as e;
use Goodby\CSV\Import\Standard\StreamFilter\ConvertMbstringEncoding;
class ConvertMbstringEncodingTest extends \PHPUnit_Framework_TestCase
{
private $internalEncodingBackup;
public function setUp()
{
$this->internalEncodingBackup = mb_internal_encoding();
}
public function tearDown()
{
mb_internal_encoding($this->internalEncodingBackup);
}
public function testGetFilterName()
{
$this->assertSame('convert.mbstring.encoding.*', ConvertMbstringEncoding::getFilterName());
}
public function testOneParameter()
{
$filterString = 'convert.mbstring.encoding.EUC-JP';
mb_internal_encoding('UTF-7');
$filter = new ConvertMbstringEncoding();
$filter->filtername = $filterString;
$filter->onCreate();
$this->assertAttributeSame('EUC-JP', 'fromCharset', $filter);
$this->assertAttributeSame('UTF-7', 'toCharset', $filter);
}
public function testTwoParameters()
{
$filterString = 'convert.mbstring.encoding.SJIS-win:UTF-8';
mb_internal_encoding('UTF-7');
$filter = new ConvertMbstringEncoding();
$filter->filtername = $filterString;
$filter->onCreate();
$this->assertAttributeSame('SJIS-win', 'fromCharset', $filter);
$this->assertAttributeSame('UTF-8', 'toCharset', $filter);
}
public function test_when_invalid_parameter_given_it_returns_false()
{
$filterString = 'convert.mbstring.encoding.@#$#!%^^';
$filter = new ConvertMbstringEncoding();
$filter->filtername = $filterString;
$this->assertFalse($filter->onCreate());
}
public function test_register_filter()
{
ConvertMbstringEncoding::register();
$filterName = ConvertMbstringEncoding::getFilterName();
$registeredFilters = stream_get_filters();
$this->assertTrue(in_array($filterName, $registeredFilters));
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Goodby\CSV\TestHelper;
class DbManager
{
private $pdo;
private $host;
private $db;
private $user;
private $pass;
public function __construct()
{
$this->host = $_SERVER['GOODBY_CSV_TEST_DB_HOST'];
$this->db = $_SERVER['GOODBY_CSV_TEST_DB_NAME_DEFAULT'];
$this->user = $_SERVER['GOODBY_CSV_TEST_DB_USER'];
$this->pass = $_SERVER['GOODBY_CSV_TEST_DB_PASS'];
$dsn = 'mysql:host=' . $this->host;
$this->pdo = new \PDO($dsn, $this->user, $this->pass);
$stmt = $this->pdo->prepare("CREATE DATABASE " . $this->db);
$stmt->execute();
}
public function __destruct()
{
$stmt = $this->pdo->prepare("DROP DATABASE " . $this->db);
$stmt->execute();
}
public function getPdo()
{
return new \PDO($this->getDsn(), $this->user, $this->pass);
}
public function getDsn()
{
return 'mysql:dbname=' . $this->db . ';host=' . $this->host;
}
public function getUser()
{
return $this->user;
}
public function getPassword()
{
return $this->pass;
}
}

View File

@ -1,213 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv;
use IteratorAggregate;
use JsonSerializable;
use League\Csv\Config\Controls;
use League\Csv\Config\Factory;
use League\Csv\Config\Output;
use League\Csv\Config\StreamFilter;
use SplFileInfo;
use SplFileObject;
/**
* An abstract class to enable basic CSV manipulation
*
* @package League.csv
* @since 4.0.0
*
*/
abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
{
/**
* UTF-8 BOM sequence
*/
const BOM_UTF8 = "\xEF\xBB\xBF";
/**
* UTF-16 BE BOM sequence
*/
const BOM_UTF16_BE = "\xFE\xFF";
/**
* UTF-16 LE BOM sequence
*/
const BOM_UTF16_LE = "\xFF\xFE";
/**
* UTF-16 BE BOM sequence
*/
const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
/**
* UTF-16 LE BOM sequence
*/
const BOM_UTF32_LE = "\x00\x00\xFF\xFE";
/**
* The constructor path
*
* can be a SplFileInfo object or the string path to a file
*
* @var \SplFileObject|string
*/
protected $path;
/**
* The file open mode flag
*
* @var string
*/
protected $open_mode;
/**
* Csv Controls Trait
*/
use Controls;
/**
* Csv Factory Trait
*/
use Factory;
/**
* Csv Ouputting Trait
*/
use Output;
/**
* Stream Filter API Trait
*/
use StreamFilter;
/**
* Create a new instance
*
* The path must be an SplFileInfo object
* an object that implements the `__toString` method
* a path to a file
*
* @param object|string $path The file path
* @param string $open_mode the file open mode flag
*/
public function __construct($path, $open_mode = 'r+')
{
ini_set('auto_detect_line_endings', '1');
$this->path = $this->normalizePath($path);
$this->open_mode = strtolower($open_mode);
$this->initStreamFilter($this->path);
}
/**
* Return a normalize path which could be a SplFileObject
* or a string path
*
* @param object|string $path the filepath
*
* @return \SplFileObject|string
*/
protected function normalizePath($path)
{
if ($path instanceof SplFileObject) {
return $path;
} elseif ($path instanceof SplFileInfo) {
return $path->getPath().'/'.$path->getBasename();
}
return trim($path);
}
/**
* The destructor
*/
public function __destruct()
{
//in case $this->path is a SplFileObject we need to remove its reference
$this->path = null;
}
/**
* Return the CSV Iterator
*
* @return \SplFileObject
*/
public function getIterator()
{
$obj = $this->path;
if (! $obj instanceof SplFileObject) {
$obj = new SplFileObject($this->getStreamFilterPath(), $this->open_mode);
}
$obj->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
$obj->setFlags($this->flags);
return $obj;
}
/**
* Create a {@link AbstractCsv} instance from another {@link AbstractCsv} object
*
* @param string $class_name the class to be instantiated
* @param string $open_mode the file open mode flag
*
* @return static
*/
protected function newInstance($class_name, $open_mode)
{
$csv = new $class_name($this->path, $open_mode);
$csv->delimiter = $this->delimiter;
$csv->enclosure = $this->enclosure;
$csv->escape = $this->escape;
$csv->encodingFrom = $this->encodingFrom;
$csv->bom = $this->bom;
return $csv;
}
/**
* Create a {@link Writer} instance from a {@link AbstractCsv} object
*
* @param string $open_mode the file open mode flag
*
* @return \League\Csv\Writer
*/
public function newWriter($open_mode = 'r+')
{
return $this->newInstance('\League\Csv\Writer', $open_mode);
}
/**
* Create a {@link Reader} instance from a {@link AbstractCsv} object
*
* @param string $open_mode the file open mode flag
*
* @return \League\Csv\Reader
*/
public function newReader($open_mode = 'r+')
{
return $this->newInstance('\League\Csv\Reader', $open_mode);
}
/**
* Validate a variable to be stringable
*
* @param object|string $str
*
* @return bool
*/
public static function isValidString($str)
{
return is_scalar($str) || (is_object($str) && method_exists($str, '__toString'));
}
}

View File

@ -1,259 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv\Config;
use CallbackFilterIterator;
use InvalidArgumentException;
use LimitIterator;
use SplFileObject;
/**
* A trait to configure and check CSV file and content
*
* @package League.csv
* @since 6.0.0
*
*/
trait Controls
{
/**
* the field delimiter (one character only)
*
* @var string
*/
protected $delimiter = ',';
/**
* the field enclosure character (one character only)
*
* @var string
*/
protected $enclosure = '"';
/**
* the field escape character (one character only)
*
* @var string
*/
protected $escape = '\\';
/**
* the \SplFileObject flags holder
*
* @var int
*/
protected $flags = SplFileObject::READ_CSV;
/**
* return a SplFileOjbect
*
* @return \SplFileOjbect
*/
abstract public function getIterator();
/**
* set the field delimeter
*
* @param string $delimiter
*
* @throws \InvalidArgumentException If $delimeter is not a single character
*
* @return $this
*/
public function setDelimiter($delimiter = ',')
{
if (1 != mb_strlen($delimiter)) {
throw new InvalidArgumentException('The delimiter must be a single character');
}
$this->delimiter = $delimiter;
return $this;
}
/**
* return the current field delimiter
*
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* detect the actual number of row according to a delimiter
*
* @param string $delimiter a CSV delimiter
* @param int $nb_rows the number of row to consider
*
* @return int
*/
protected function fetchRowsCountByDelimiter($delimiter, $nb_rows = 1)
{
$iterator = $this->getIterator();
$iterator->setCsvControl($delimiter, $this->enclosure, $this->escape);
//"reduce" the csv length to a maximum of $nb_rows
$iterator = new LimitIterator($iterator, 0, $nb_rows);
//return the parse rows
$iterator = new CallbackFilterIterator($iterator, function ($row) {
return is_array($row) && count($row) > 1;
});
return count(iterator_to_array($iterator, false), COUNT_RECURSIVE);
}
/**
* Detect the CSV file delimiter
*
* @param int $nb_rows
* @param string[] $delimiters additional delimiters
*
* @throws \InvalidArgumentException If $nb_rows value is invalid
*
* @return string[]
*/
public function detectDelimiterList($nb_rows = 1, array $delimiters = [])
{
$nb_rows = filter_var($nb_rows, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if (! $nb_rows) {
throw new InvalidArgumentException('`$nb_rows` must be a valid positive integer');
}
$delimiters = array_filter($delimiters, function ($str) {
return 1 == mb_strlen($str);
});
$delimiters = array_merge([$this->delimiter, ',', ';', "\t"], $delimiters);
$delimiters = array_unique($delimiters);
$res = array_fill_keys($delimiters, 0);
array_walk($res, function (&$value, $delim) use ($nb_rows) {
$value = $this->fetchRowsCountByDelimiter($delim, $nb_rows);
});
arsort($res, SORT_NUMERIC);
return array_keys(array_filter($res));
}
/**
* set the field enclosure
*
* @param string $enclosure
*
* @throws \InvalidArgumentException If $enclosure is not a single character
*
* @return $this
*/
public function setEnclosure($enclosure = '"')
{
if (1 != mb_strlen($enclosure)) {
throw new InvalidArgumentException('The enclosure must be a single character');
}
$this->enclosure = $enclosure;
return $this;
}
/**
* return the current field enclosure
*
* @return string
*/
public function getEnclosure()
{
return $this->enclosure;
}
/**
* set the field escape character
*
* @param string $escape
*
* @throws \InvalidArgumentException If $escape is not a single character
*
* @return $this
*/
public function setEscape($escape = "\\")
{
if (1 != mb_strlen($escape)) {
throw new InvalidArgumentException('The escape character must be a single character');
}
$this->escape = $escape;
return $this;
}
/**
* return the current field escape character
*
* @return string
*/
public function getEscape()
{
return $this->escape;
}
/**
* Set the Flags associated to the CSV SplFileObject
*
* @param int $flags
*
* @throws \InvalidArgumentException If the argument is not a valid integer
*
* @return $this
*/
public function setFlags($flags)
{
if (false === filter_var($flags, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
throw new InvalidArgumentException('you should use a `SplFileObject` Constant');
}
$this->flags = $flags|SplFileObject::READ_CSV|SplFileObject::DROP_NEW_LINE;
return $this;
}
/**
* Returns the file Flags
*
* @return int
*/
public function getFlags()
{
return $this->flags;
}
/**
* Returns the BOM sequence of the given CSV
*
* @return string
*/
public function getInputBOM()
{
$bom = [
self::BOM_UTF8,
self::BOM_UTF16_BE,
self::BOM_UTF16_LE,
self::BOM_UTF32_BE,
self::BOM_UTF32_LE
];
$csv = $this->getIterator();
$csv->rewind();
$line = $csv->fgets();
$res = array_filter($bom, function ($sequence) use ($line) {
return strpos($line, $sequence) === 0;
});
return array_shift($res);
}
}

View File

@ -1,115 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.X.X
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv\Config;
use InvalidArgumentException;
use SplFileInfo;
use SplFileObject;
use SplTempFileObject;
/**
* A trait to facilate class instantiation
*
* @package League.csv
* @since 6.X.X
*
*/
trait Factory
{
/**
* Create a {@link AbstractCsv} from a string
*
* The path can be:
* - an SplFileInfo,
* - a SplFileObject,
* - an object that implements the `__toString` method,
* - a string
*
* BUT NOT a SplTempFileObject
*
* <code>
*<?php
* $csv = new Reader::createFromPath('/path/to/file.csv', 'a+');
* $csv = new Reader::createFromPath(new SplFileInfo('/path/to/file.csv'));
* $csv = new Reader::createFromPath(new SplFileObject('/path/to/file.csv'), 'rb');
*
* ?>
* </code>
*
* @param object|string $path file path
* @param string $open_mode the file open mode flag
*
* @throws \InvalidArgumentException If $path is a \SplTempFileObject object
*
* @return static
*/
public static function createFromPath($path, $open_mode = 'r+')
{
if ($path instanceof SplTempFileObject) {
throw new InvalidArgumentException('an `SplTempFileObject` object does not contain a valid path');
} elseif ($path instanceof SplFileInfo) {
$path = $path->getPath().'/'.$path->getBasename();
}
return new static(trim($path), $open_mode);
}
/**
* Create a {@link AbstractCsv} from a SplFileObject
*
* The path can be:
* - a SplFileObject,
* - a SplTempFileObject
*
* <code>
*<?php
* $csv = new Writer::createFromFileObject(new SplFileInfo('/path/to/file.csv'));
* $csv = new Writer::createFromFileObject(new SplTempFileObject);
*
* ?>
* </code>
*
* @param SplFileObject $obj
*
* @return static
*/
public static function createFromFileObject(SplFileObject $obj)
{
return new static($obj);
}
/**
* Create a {@link AbstractCsv} from a string
*
* The string must be an object that implements the `__toString` method,
* or a string
*
* @param string|object $str the string
*
* @throws \InvalidArgumentException If the data provided is invalid
*
* @return static
*/
public static function createFromString($str)
{
if (! self::isValidString($str)) {
throw new InvalidArgumentException(
'the submitted data must be a string or an object implementing the `__toString` method'
);
}
$obj = new SplTempFileObject();
$obj->fwrite(rtrim($str)."\n");
return static::createFromFileObject($obj);
}
}

View File

@ -1,217 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv\Config;
use DomDocument;
use InvalidArgumentException;
use League\Csv\Iterator\MapIterator;
use Traversable;
/**
* A trait to output CSV
*
* @package League.csv
* @since 6.3.0
*
*/
trait Output
{
/**
* Charset Encoding for the CSV
*
* @var string
*/
protected $encodingFrom = 'UTF-8';
/**
* BOM sequence for Outputting the CSV
* @var string
*/
protected $bom;
/**
* Return the CSV Iterator
*
* @return \SplFileObject
*/
abstract public function getIterator();
/**
* JsonSerializable Interface
*
* @return array
*/
public function jsonSerialize()
{
return iterator_to_array($this->convertToUtf8($this->getIterator()), false);
}
/**
* Set the CSV encoding charset
*
* @param string $str
*
* @return static
*/
public function setEncodingFrom($str)
{
$str = str_replace('_', '-', $str);
$str = filter_var($str, FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_STRIP_LOW|FILTER_FLAG_STRIP_HIGH]);
if (empty($str)) {
throw new InvalidArgumentException('you should use a valid charset');
}
$this->encodingFrom = strtoupper($str);
return $this;
}
/**
* Get the source CSV encoding charset
*
* @return string
*/
public function getEncodingFrom()
{
return $this->encodingFrom;
}
/**
* Set the BOM sequence to prepend the CSV on output
*
* @param string $str The BOM sequence
*
* @return static
*/
public function setOutputBOM($str = null)
{
if (empty($str)) {
$this->bom = null;
return $this;
}
$str = (string) $str;
$str = trim($str);
$this->bom = $str;
return $this;
}
/**
* Returns the BOM sequence in use on Output methods
*
* @return string
*/
public function getOutputBOM()
{
return $this->bom;
}
/**
* Output all data on the CSV file
*
* @param string $filename CSV downloaded name if present adds extra headers
*/
public function output($filename = null)
{
$iterator = $this->getIterator();
$iterator->rewind();
//@codeCoverageIgnoreStart
if (! is_null($filename) && self::isValidString($filename)) {
$filename = trim($filename);
$filename = filter_var($filename, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: binary");
header('Content-Disposition: attachment; filename="'.$filename);
}
//@codeCoverageIgnoreEnd
echo $this->bom;
$iterator->fpassthru();
}
/**
* Retrieves the CSV content
*
* @return string
*/
public function __toString()
{
ob_start();
$this->output();
return ob_get_clean();
}
/**
* Return a HTML table representation of the CSV Table
*
* @param string $class_name optional classname
*
* @return string
*/
public function toHTML($class_name = 'table-csv-data')
{
$doc = $this->toXML('table', 'tr', 'td');
$doc->documentElement->setAttribute('class', $class_name);
return $doc->saveHTML($doc->documentElement);
}
/**
* transform a CSV into a XML
*
* @param string $root_name XML root node name
* @param string $row_name XML row node name
* @param string $cell_name XML cell node name
*
* @return \DomDocument
*/
public function toXML($root_name = 'csv', $row_name = 'row', $cell_name = 'cell')
{
$doc = new DomDocument('1.0', 'UTF-8');
$root = $doc->createElement($root_name);
$iterator = $this->convertToUtf8($this->getIterator());
foreach ($iterator as $row) {
$item = $doc->createElement($row_name);
array_walk($row, function ($value) use (&$item, $doc, $cell_name) {
$content = $doc->createTextNode($value);
$cell = $doc->createElement($cell_name);
$cell->appendChild($content);
$item->appendChild($cell);
});
$root->appendChild($item);
}
$doc->appendChild($root);
return $doc;
}
/**
* Convert Csv file into UTF-8
*
* @return \Traversable
*/
protected function convertToUtf8(Traversable $iterator)
{
if (strpos($this->encodingFrom, 'UTF-8') !== false) {
return $iterator;
}
return new MapIterator($iterator, function ($row) {
foreach ($row as &$value) {
$value = mb_convert_encoding($value, 'UTF-8', $this->encodingFrom);
}
unset($value);
return $row;
});
}
}

View File

@ -1,271 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv\Config;
use LogicException;
use OutOfBoundsException;
use SplFileInfo;
/**
* A Trait to ease PHP Stream Filters manipulation
* with a SplFileObject
*
* @package League.csv
* @since 6.0.0
*
*/
trait StreamFilter
{
/**
* collection of stream filters
*
* @var array
*/
protected $stream_filters = [];
/**
* Stream filtering mode to apply on all filters
*
* @var int
*/
protected $stream_filter_mode = STREAM_FILTER_ALL;
/**
*the real path
*
* @var string the real path to the file
*
*/
protected $stream_uri;
/**
* Internal path setter
*
* The path must be an SplFileInfo object
* an object that implements the `__toString` method
* a path to a file
*
* @param \SplFileObject|string $path The file path
*/
protected function initStreamFilter($path)
{
if (! is_string($path)) {
$this->stream_uri = null;
$this->stream_filters = [];
return;
}
$this->extractStreamSettings($path);
}
/**
* Extract Available stream settings from $path
*
* @param string $path the file path
*/
protected function extractStreamSettings($path)
{
if (! preg_match(
',^php://filter/(?P<mode>:?read=|write=)?(?P<filters>.*?)/resource=(?P<resource>.*)$,i',
$path,
$matches
)) {
$this->stream_uri = $path;
$this->stream_filters = [];
return;
}
$matches['mode'] = strtolower($matches['mode']);
$mode = STREAM_FILTER_ALL;
if ('write=' == $matches['mode']) {
$mode = STREAM_FILTER_WRITE;
} elseif ('read=' == $matches['mode']) {
$mode = STREAM_FILTER_READ;
}
$this->stream_filter_mode = $mode;
$this->stream_uri = $matches['resource'];
$this->stream_filters = explode('|', $matches['filters']);
}
/**
* Check if the trait methods can be used
*
* @throws \LogicException If the API can not be use
*/
protected function assertStreamable()
{
if (! is_string($this->stream_uri)) {
throw new LogicException('The stream filter API can not be used');
}
}
/**
* Tells whether the stream filter capabilities can be used
*
* @return bool
*/
public function isActiveStreamFilter()
{
return is_string($this->stream_uri);
}
/**
* stream filter mode Setter
*
* Set the new Stream Filter mode and remove all
* previously attached stream filters
*
* @param int $mode
*
* @throws \OutOfBoundsException If the mode is invalid
*
* @return $this
*/
public function setStreamFilterMode($mode)
{
$this->assertStreamable();
if (! in_array($mode, [STREAM_FILTER_ALL, STREAM_FILTER_READ, STREAM_FILTER_WRITE])) {
throw new OutOfBoundsException('the $mode should be a valid `STREAM_FILTER_*` constant');
}
$this->stream_filter_mode = $mode;
$this->stream_filters = [];
return $this;
}
/**
* stream filter mode getter
*
* @return int
*/
public function getStreamFilterMode()
{
$this->assertStreamable();
return $this->stream_filter_mode;
}
/**
* append a stream filter
*
* @param string $filter_name a string or an object that implements the '__toString' method
*
* @return $this
*/
public function appendStreamFilter($filter_name)
{
$this->assertStreamable();
$this->stream_filters[] = $this->sanitizeStreamFilter($filter_name);
return $this;
}
/**
* prepend a stream filter
*
* @param string $filter_name a string or an object that implements the '__toString' method
*
* @return $this
*/
public function prependStreamFilter($filter_name)
{
$this->assertStreamable();
array_unshift($this->stream_filters, $this->sanitizeStreamFilter($filter_name));
return $this;
}
/**
* Sanitize the stream filter name
*
* @param string $filter_name the stream filter name
*
* @return string
*/
protected function sanitizeStreamFilter($filter_name)
{
$this->assertStreamable();
$filter_name = (string) $filter_name;
return trim($filter_name);
}
/**
* Detect if the stream filter is already present
*
* @param string $filter_name
*
* @return bool
*/
public function hasStreamFilter($filter_name)
{
$this->assertStreamable();
return false !== array_search($filter_name, $this->stream_filters, true);
}
/**
* Remove a filter from the collection
*
* @param string $filter_name
*
* @return $this
*/
public function removeStreamFilter($filter_name)
{
$this->assertStreamable();
$res = array_search($filter_name, $this->stream_filters, true);
if (false !== $res) {
unset($this->stream_filters[$res]);
}
return $this;
}
/**
* Remove all registered stream filter
*
* @return $this
*/
public function clearStreamFilter()
{
$this->assertStreamable();
$this->stream_filters = [];
return $this;
}
/**
* Return the filter path
*
* @return string
*/
protected function getStreamFilterPath()
{
$this->assertStreamable();
if (! $this->stream_filters) {
return $this->stream_uri;
}
$prefix = '';
if (STREAM_FILTER_READ == $this->stream_filter_mode) {
$prefix = 'read=';
} elseif (STREAM_FILTER_WRITE == $this->stream_filter_mode) {
$prefix = 'write=';
}
return 'php://filter/'.$prefix.implode('|', $this->stream_filters).'/resource='.$this->stream_uri;
}
}

View File

@ -1,57 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv\Iterator;
use IteratorIterator;
use Traversable;
/**
* A simple MapIterator
*
* @package League.csv
* @since 3.3.0
* @internal used internally to modify CSV content
*
*/
class MapIterator extends IteratorIterator
{
/**
* The function to be apply on all InnerIterator element
*
* @var callable
*/
private $callable;
/**
* The Constructor
*
* @param Traversable $iterator
* @param callable $callable
*/
public function __construct(Traversable $iterator, callable $callable)
{
parent::__construct($iterator);
$this->callable = $callable;
}
/**
* Get the value of the current element
*/
public function current()
{
$iterator = $this->getInnerIterator();
$callable = $this->callable;
return $callable($iterator->current(), $iterator->key(), $iterator);
}
}

View File

@ -1,269 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv\Iterator;
use ArrayIterator;
use CallbackFilterIterator;
use InvalidArgumentException;
use Iterator;
use LimitIterator;
/**
* A Trait to Query rows against a SplFileObject
*
* @package League.csv
* @since 4.2.1
*
*/
trait Query
{
/**
* Callable function to filter the iterator
*
* @var array
*/
protected $iterator_filters = [];
/**
* Callable function to sort the ArrayObject
*
* @var callable
*/
protected $iterator_sort_by = [];
/**
* iterator Offset
*
* @var int
*/
protected $iterator_offset = 0;
/**
* iterator maximum length
*
* @var int
*/
protected $iterator_limit = -1;
/**
* Set LimitIterator Offset
*
* @param $offset
*
* @return $this
*/
public function setOffset($offset = 0)
{
if (false === filter_var($offset, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
throw new InvalidArgumentException('the offset must be a positive integer or 0');
}
$this->iterator_offset = $offset;
return $this;
}
/**
* Set LimitInterator Count
*
* @param int $limit
*
* @return $this
*/
public function setLimit($limit = -1)
{
if (false === filter_var($limit, FILTER_VALIDATE_INT, ['options' => ['min_range' => -1]])) {
throw new InvalidArgumentException('the limit must an integer greater or equals to -1');
}
$this->iterator_limit = $limit;
return $this;
}
/**
* Sort the Iterator
*
* @param \Iterator $iterator
*
* @return \LimitIterator
*/
protected function applyIteratorInterval(Iterator $iterator)
{
if (0 == $this->iterator_offset && -1 == $this->iterator_limit) {
return $iterator;
}
$offset = $this->iterator_offset;
$limit = $this->iterator_limit;
$this->iterator_limit = -1;
$this->iterator_offset = 0;
return new LimitIterator($iterator, $offset, $limit);
}
/**
* Set an Iterator sorting callable function
*
* @param callable $callable
*
* @return $this
*/
public function addSortBy(callable $callable)
{
$this->iterator_sort_by[] = $callable;
return $this;
}
/**
* Remove a callable from the collection
*
* @param callable $callable
*
* @return $this
*/
public function removeSortBy(callable $callable)
{
$res = array_search($callable, $this->iterator_sort_by, true);
if (false !== $res) {
unset($this->iterator_sort_by[$res]);
}
return $this;
}
/**
* Detect if the callable is already registered
*
* @param callable $callable
*
* @return bool
*/
public function hasSortBy(callable $callable)
{
return false !== array_search($callable, $this->iterator_sort_by, true);
}
/**
* Remove all registered callable
*
* @return $this
*/
public function clearSortBy()
{
$this->iterator_sort_by = [];
return $this;
}
/**
* Sort the Iterator
*
* @param \Iterator $iterator
*
* @return \ArrayIterator
*/
protected function applyIteratorSortBy(Iterator $iterator)
{
if (! $this->iterator_sort_by) {
return $iterator;
}
$nb_callbacks = count($this->iterator_sort_by);
$this->iterator_sort_by = array_values($this->iterator_sort_by);
$res = iterator_to_array($iterator, false);
uasort($res, function ($rowA, $rowB) use ($nb_callbacks) {
$res = 0;
$index = 0;
while ($index < $nb_callbacks && 0 === $res) {
$res = $this->iterator_sort_by[$index]($rowA, $rowB);
++$index;
}
return $res;
});
$this->clearSortBy();
return new ArrayIterator($res);
}
/**
* Set the Iterator filter method
*
* @param callable $callable
*
* @return $this
*/
public function addFilter(callable $callable)
{
$this->iterator_filters[] = $callable;
return $this;
}
/**
* Remove a filter from the callable collection
*
* @param callable $callable
*
* @return $this
*/
public function removeFilter(callable $callable)
{
$res = array_search($callable, $this->iterator_filters, true);
if (false !== $res) {
unset($this->iterator_filters[$res]);
}
return $this;
}
/**
* Detect if the callable filter is already registered
*
* @param callable $callable
*
* @return bool
*/
public function hasFilter(callable $callable)
{
return false !== array_search($callable, $this->iterator_filters, true);
}
/**
* Remove all registered callable filter
*
* @return $this
*/
public function clearFilter()
{
$this->iterator_filters = [];
return $this;
}
/**
* Filter the Iterator
*
* @param \Iterator $iterator
*
* @return \Iterator
*/
protected function applyIteratorFilter(Iterator $iterator)
{
foreach ($this->iterator_filters as $callable) {
$iterator = new CallbackFilterIterator($iterator, $callable);
}
$this->clearFilter();
return $iterator;
}
}

View File

@ -1,269 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv;
use CallbackFilterIterator;
use InvalidArgumentException;
use Iterator;
use League\Csv\Iterator\MapIterator;
use League\Csv\Iterator\Query;
use LimitIterator;
/**
* A class to manage extracting and filtering a CSV
*
* @package League.csv
* @since 3.0.0
*
*/
class Reader extends AbstractCsv
{
/**
* Iterator Query Trait
*/
use Query;
/**
* {@ihneritdoc}
*/
protected $stream_filter_mode = STREAM_FILTER_READ;
/**
* Return a Filtered Iterator
*
* @param callable $callable a callable function to be applied to each Iterator item
*
* @return \Traversable
*/
public function query(callable $callable = null)
{
$iterator = new CallbackFilterIterator($this->getIterator(), function ($row) {
return is_array($row);
});
$iterator = $this->applyIteratorFilter($iterator);
$iterator = $this->applyIteratorSortBy($iterator);
$iterator = $this->applyIteratorInterval($iterator);
if (! is_null($callable)) {
$iterator = new MapIterator($iterator, $callable);
}
return $iterator;
}
/**
* Apply a callback function on the CSV
*
* The callback function must return TRUE in order to continue
* iterating over the iterator.
*
* @param callable $callable The callback function
*
* @return int the iteration count
*/
public function each(callable $callable)
{
$index = 0;
$iterator = $this->query();
$iterator->rewind();
while ($iterator->valid() && true === $callable($iterator->current(), $iterator->key(), $iterator)) {
++$index;
$iterator->next();
}
return $index;
}
/**
* Return a single column from the CSV data
*
* The callable function will be applied to each value to be return
*
* @param int $column_index field Index
* @param callable $callable a callable function
*
* @throws \InvalidArgumentException If the column index is not a positive integer or 0
*
* @return array
*/
public function fetchColumn($column_index = 0, callable $callable = null)
{
if (false === filter_var($column_index, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
throw new InvalidArgumentException(
'the column index must be a positive integer or 0'
);
}
$iterator = $this->query($callable);
$iterator = new MapIterator($iterator, function ($row) use ($column_index) {
if (! array_key_exists($column_index, $row)) {
return null;
}
return $row[$column_index];
});
return iterator_to_array($iterator, false);
}
/**
* Return a single row from the CSV
*
* @param int $offset
*
* @throws \InvalidArgumentException If the $offset is not a valid Integer
*
* @return array
*/
public function fetchOne($offset = 0)
{
$this->setOffset($offset);
$this->setLimit(1);
$iterator = $this->query();
$iterator->rewind();
return (array) $iterator->current();
}
/**
* Return a sequential array of all CSV lines
*
* The callable function will be applied to each Iterator item
*
* @param callable $callable a callable function
*
* @return array
*/
public function fetchAll(callable $callable = null)
{
return iterator_to_array($this->query($callable), false);
}
/**
* Return a sequential array of all CSV lines;
*
* The rows are presented as associated arrays
* The callable function will be applied to each Iterator item
*
* @param array|int $offset_or_keys the name for each key member OR the row Index to be
* used as the associated named keys
* @param callable $callable a callable function
*
* @throws \InvalidArgumentException If the submitted keys are invalid
*
* @return array
*/
public function fetchAssoc($offset_or_keys = 0, callable $callable = null)
{
$keys = $this->getAssocKeys($offset_or_keys);
$iterator = $this->query($callable);
$iterator = new MapIterator($iterator, function ($row) use ($keys) {
return static::combineArray($keys, $row);
});
return iterator_to_array($iterator, false);
}
/**
* Select the array to be used as key for the fetchAssoc method
* @param array|int $offset_or_keys the assoc key OR the row Index to be used
* as the key index
*
* @throws \InvalidArgumentException If the row index and/or the resulting array is invalid
*
* @return array
*/
protected function getAssocKeys($offset_or_keys)
{
$res = $offset_or_keys;
if (! is_array($res)) {
$res = $this->getRow($offset_or_keys);
$this->addFilter(function ($row, $rowIndex) use ($offset_or_keys) {
return is_array($row) && $rowIndex != $offset_or_keys;
});
}
if (! $this->isValidAssocKeys($res)) {
throw new InvalidArgumentException(
'Use a flat non empty array with unique string values'
);
}
return $res;
}
/**
* Return a single row from the CSV without filtering
*
* @param int $offset
*
* @throws \InvalidArgumentException If the $offset is not valid or the row does not exist
*
* @return array
*/
protected function getRow($offset)
{
if (false === filter_var($offset, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
throw new InvalidArgumentException('the row index must be a positive integer or 0');
}
$iterator = new LimitIterator($this->getIterator(), $offset, 1);
$iterator->rewind();
$res = $iterator->current();
if (is_null($res)) {
throw new InvalidArgumentException('the specified row does not exist');
}
return $res;
}
/**
* Validate the array to be used by the fetchAssoc method
*
* @param array $keys
*
* @return boolean
*/
protected function isValidAssocKeys(array $keys)
{
$validKeys = array_unique(array_filter($keys, function ($value) {
return self::isValidString($value);
}));
return count($keys) && $keys === $validKeys;
}
/**
* Intelligent Array Combine
*
* add or remove values from the $value array to
* match array $keys length before using PHP array_combine function
*
* @param array $keys
* @param array $value
*
* @return array
*/
protected static function combineArray(array $keys, array $value)
{
$nbKeys = count($keys);
$diff = $nbKeys - count($value);
if ($diff > 0) {
$value = array_merge($value, array_fill(0, $diff, null));
} elseif ($diff < 0) {
$value = array_slice($value, 0, $nbKeys);
}
return array_combine($keys, $value);
}
}

View File

@ -1,355 +0,0 @@
<?php
/**
* This file is part of the League.csv library
*
* @license http://opensource.org/licenses/MIT
* @link https://github.com/thephpleague/csv/
* @version 6.3.0
* @package League.csv
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Csv;
use InvalidArgumentException;
use OutOfBoundsException;
use RuntimeException;
use SplFileObject;
use Traversable;
/**
* A class to manage data insertion into a CSV
*
* @package League.csv
* @since 4.0.0
*
*/
class Writer extends AbstractCsv
{
/**
* set null handling mode to throw exception
*/
const NULL_AS_EXCEPTION = 1;
/**
* set null handling mode to remove cell
*/
const NULL_AS_SKIP_CELL = 2;
/**
* set null handling mode to convert null into empty string
*/
const NULL_AS_EMPTY = 3;
/**
* the object current null handling mode
*
* @var int
*/
protected $null_handling_mode = self::NULL_AS_EXCEPTION;
/**
* The number of column per row
*
* @var int
*/
protected $columns_count = -1;
/**
* should the class detect the column count based the inserted row
*
* @var bool
*/
protected $detect_columns_count = false;
/**
* {@ihneritdoc}
*/
protected $stream_filter_mode = STREAM_FILTER_WRITE;
/**
* The CSV object holder
*
* @var \SplFileObject
*/
protected $csv;
/**
* newline character
*
* @var string
*/
protected $newline = "\n";
/**
* Tell the class how to handle null value
*
* @param int $value a Writer null behavior constant
*
* @throws \OutOfBoundsException If the Integer is not valid
*
* @return static
*/
public function setNullHandlingMode($value)
{
if (!in_array($value, [self::NULL_AS_SKIP_CELL, self::NULL_AS_EXCEPTION, self::NULL_AS_EMPTY])) {
throw new OutOfBoundsException('invalid value for null handling');
}
$this->null_handling_mode = $value;
return $this;
}
/**
* null handling getter
*
* @return int
*/
public function getNullHandlingMode()
{
return $this->null_handling_mode;
}
/**
* set the newline sequence characters
*
* @param string $newline
*
* @return static
*/
public function setNewline($newline)
{
$this->newline = (string) $newline;
return $this;
}
/**
* return the current newline sequence characters
*
* @return string
*/
public function getNewline()
{
return $this->newline;
}
/**
* Set Inserted row column count
*
* @param int $value
*
* @throws \InvalidArgumentException If $value is lesser than -1
*
* @return static
*/
public function setColumnsCount($value)
{
if (false === filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => -1]])) {
throw new InvalidArgumentException('the column count must an integer greater or equals to -1');
}
$this->detect_columns_count = false;
$this->columns_count = $value;
return $this;
}
/**
* Column count getter
*
* @return int
*/
public function getColumnsCount()
{
return $this->columns_count;
}
/**
* The method will set the $columns_count property according to the next inserted row
* and therefore will also validate the next line whatever length it has no matter
* the current $columns_count property value.
*
* @return static
*/
public function autodetectColumnsCount()
{
$this->detect_columns_count = true;
return $this;
}
/**
* Add multiple lines to the CSV your are generating
*
* a simple helper/Wrapper method around insertOne
*
* @param \Traversable|array $rows a multidimentional array or a Traversable object
*
* @throws \InvalidArgumentException If the given rows format is invalid
*
* @return static
*/
public function insertAll($rows)
{
if (! is_array($rows) && ! $rows instanceof Traversable) {
throw new InvalidArgumentException(
'the provided data must be an array OR a \Traversable object'
);
}
foreach ($rows as $row) {
$this->insertOne($row);
}
return $this;
}
/**
* Add a new CSV row to the generated CSV
*
* @param string[]|string $data a string, an array or an object implementing to '__toString' method
*
* @throws \InvalidArgumentException If the given row is invalid
*
* @return static
*/
public function insertOne($data)
{
$data = $this->validateRow($data);
$data = $this->sanitizeColumnsContent($data);
if (! $this->isColumnsCountConsistent($data)) {
throw new RuntimeException(
'You are trying to add '.count($data).' columns to a CSV
that requires '.$this->columns_count.' columns per row.'
);
}
$csv = $this->getCsv();
$csv->fputcsv($data, $this->delimiter, $this->enclosure);
if ("\n" !== $this->newline) {
$csv->fseek(-1, SEEK_CUR);
$csv->fwrite($this->newline);
}
return $this;
}
/**
* Is the submitted row valid
*
* @param string[]|string $row
*
* @throws \InvalidArgumentException If the given $row is not valid
*
* @return array
*/
protected function validateRow($row)
{
if (! is_array($row)) {
$row = str_getcsv((string) $row, $this->delimiter, $this->enclosure, $this->escape);
}
array_walk($row, function ($value) {
if (! $this->isConvertibleContent($value)) {
throw new InvalidArgumentException(
'the values are not convertible into strings'
);
}
});
return $row;
}
/**
* Check if a given value can be added into a CSV cell
*
* The value MUST respect the null handling mode
* The valie MUST be convertible into a string
*
* @param string|null $value the value to be added
*
* @return bool
*/
protected function isConvertibleContent($value)
{
return (is_null($value) && self::NULL_AS_EXCEPTION != $this->null_handling_mode)
|| self::isValidString($value);
}
/**
* Format the row according to the null handling behavior
*
* @param array $row
*
* @return array
*/
protected function sanitizeColumnsContent(array $row)
{
if (self::NULL_AS_EXCEPTION == $this->null_handling_mode) {
return $row;
} elseif (self::NULL_AS_EMPTY == $this->null_handling_mode) {
return str_replace(null, '', $row);
}
return array_filter($row, function ($value) {
return !is_null($value);
});
}
/**
* Check column count consistency
*
* @param array $row the row to be added to the CSV
*
* @return bool
*/
protected function isColumnsCountConsistent(array $row)
{
if ($this->detect_columns_count) {
$this->columns_count = count($row);
$this->detect_columns_count = false;
return true;
} elseif (-1 == $this->columns_count) {
return true;
}
return count($row) == $this->columns_count;
}
/**
* set the csv container as a SplFileObject instance
* insure we use the same object for insertion to
* avoid loosing the cursor position
*
* @return \SplFileObject
*/
protected function getCsv()
{
if (is_null($this->csv)) {
$this->csv = $this->getIterator();
}
return $this->csv;
}
/**
* Tells whether the stream filter capabilities can be used
*
* @return bool
*/
public function isActiveStreamFilter()
{
return parent::isActiveStreamFilter() && is_null($this->csv);
}
/**
* The destructor
*/
public function __destruct()
{
$this->csv = null;
parent::__destruct();
}
}

View File

@ -84,6 +84,15 @@ class Csv implements ProcessingInterface{
return $this->csv_object->toHTML('table-csv-data with-header');
}//getTable()
/**
* 獲取CSV對象
* @return mixed
*/
public function getCsvObject()
{
return $this->csv_object;
}
/**
* 檢查文件內容是否規則
* @return array|bool