replace CSV plugin
This commit is contained in:
parent
d323ccd1ff
commit
6eae29d926
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Goodby\CSV\Export\Protocol\Exception;
|
||||
|
||||
/**
|
||||
* Throws if it is unable to write CSV file
|
||||
*/
|
||||
class IOException extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 >= 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 >= 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 >= 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 >= 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 >= 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();
|
||||
}
|
||||
}
|
|
@ -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 >= 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 >= 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 >= 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 >= 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 >= 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Goodby\CSV\Export\Standard\Exception;
|
||||
|
||||
class StrictViolationException extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
あ,い,う,え,お
|
|
|
@ -0,0 +1,9 @@
|
|||
"line1
|
||||
line2
|
||||
line3",single-line
|
||||
"line1
|
||||
line2
|
||||
line3",single-line
|
||||
"line1
|
||||
line2
|
||||
line3",single-line
|
|
|
@ -0,0 +1,2 @@
|
|||
✔,✔,✔
|
||||
★,★,★
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Goodby\CSV\Import\Protocol\Exception;
|
||||
|
||||
/**
|
||||
* Throws if csv file not found
|
||||
*/
|
||||
class CsvFileNotFoundException extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Goodby\CSV\Import\Protocol\Exception;
|
||||
|
||||
/**
|
||||
* Invalid lexical Exception
|
||||
*/
|
||||
class InvalidLexicalException extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Goodby\CSV\Import\Protocol;
|
||||
|
||||
/**
|
||||
* Interface of the Interpreter
|
||||
*/
|
||||
interface InterpreterInterface
|
||||
{
|
||||
/**
|
||||
* @param $line
|
||||
* @return void
|
||||
*/
|
||||
public function interpret($line);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Goodby\CSV\Import\Standard\Exception;
|
||||
|
||||
class StrictViolationException extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) . ');';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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')));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
value1:value2:value3
|
||||
value4:value5:value6
|
|
|
@ -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
|
|
|
@ -0,0 +1 @@
|
|||
a,b,c
d,e,f
|
|
|
@ -0,0 +1,4 @@
|
|||
あ,い,う,え,お
|
||||
日本語,日本語,日本語,日本語,日本語
|
||||
ぱ,ぴ,ぷ,ぺ,ぽ
|
||||
"""quoted""",a'quote',"a, b and c",,
|
|
|
@ -0,0 +1,7 @@
|
|||
value1 value2 value3
|
||||
value4 value5 value6
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
✔,✔,✔,✔
|
||||
★,★,★,★
|
||||
유,니,코,드
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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')));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue