├── src └── Goodby │ └── CSV │ ├── Import │ ├── Tests │ │ ├── Standard │ │ │ ├── Join │ │ │ │ ├── csv_files │ │ │ │ │ ├── mac-excel.csv │ │ │ │ │ ├── utf-8.csv │ │ │ │ │ ├── colon-separated.csv │ │ │ │ │ ├── tab-separated.csv │ │ │ │ │ ├── sjis.csv │ │ │ │ │ └── issue-5.csv │ │ │ │ ├── CSVFiles.php │ │ │ │ ├── Observer │ │ │ │ │ └── PdoObserverTest.php │ │ │ │ └── LexerTest.php │ │ │ ├── SandboxDirectoryManager.php │ │ │ └── Unit │ │ │ │ ├── Observer │ │ │ │ └── SqlObserverTest.php │ │ │ │ ├── LexerConfigTest.php │ │ │ │ ├── StreamFilter │ │ │ │ └── ConvertMbstringEncodingTest.php │ │ │ │ └── InterpreterTest.php │ │ └── Protocol │ │ │ ├── LexerTest.php │ │ │ └── InterpreterTest.php │ ├── Standard │ │ ├── Exception │ │ │ └── StrictViolationException.php │ │ ├── Observer │ │ │ ├── SqlObserver.php │ │ │ └── PdoObserver.php │ │ ├── Lexer.php │ │ ├── Interpreter.php │ │ ├── StreamFilter │ │ │ └── ConvertMbstringEncoding.php │ │ └── LexerConfig.php │ └── Protocol │ │ ├── Exception │ │ ├── InvalidLexicalException.php │ │ └── CsvFileNotFoundException.php │ │ ├── InterpreterInterface.php │ │ └── LexerInterface.php │ ├── Export │ ├── Tests │ │ ├── Standard │ │ │ ├── Join │ │ │ │ ├── csv_files │ │ │ │ │ ├── utf-8.csv │ │ │ │ │ ├── euc-jp.csv │ │ │ │ │ └── multiple-lines.csv │ │ │ │ ├── Collection │ │ │ │ │ └── PdoCollectionTest.php │ │ │ │ ├── UsageTest.php │ │ │ │ └── ExporterTest.php │ │ │ └── Unit │ │ │ │ ├── Collection │ │ │ │ ├── SampleAggIterator.php │ │ │ │ └── CallbackCollectionTest.php │ │ │ │ └── ExporterConfigTest.php │ │ └── Protocol │ │ │ └── ExporterInterfaceTest.php │ ├── Standard │ │ ├── Exception │ │ │ └── StrictViolationException.php │ │ ├── Collection │ │ │ ├── PdoCollection.php │ │ │ └── CallbackCollection.php │ │ ├── CsvFileObject.php │ │ ├── Exporter.php │ │ └── ExporterConfig.php │ └── Protocol │ │ ├── Exception │ │ └── IOException.php │ │ └── ExporterInterface.php │ └── TestHelper │ └── DbManager.php ├── example ├── temperature.tsv ├── user.csv ├── insert-or-update-user-for-mysql.php ├── export_from_database_via_pdo.php ├── tsv-sample.php ├── import_from_database_via_pdo.php └── sample.php ├── .gitignore ├── scripts └── bundle-devtools.sh ├── .travis.yml ├── phpunit-bootstrap.php ├── LICENSE ├── composer.json ├── phpunit.xml.dist └── README.md /src/Goodby/CSV/Import/Tests/Standard/Join/csv_files/mac-excel.csv: -------------------------------------------------------------------------------- 1 | a,b,c d,e,f -------------------------------------------------------------------------------- /example/temperature.tsv: -------------------------------------------------------------------------------- 1 | 9 Tokyo 2 | 27 Singapore 3 | -5 Seoul 4 | 7 Shanghai 5 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Tests/Standard/Join/csv_files/utf-8.csv: -------------------------------------------------------------------------------- 1 | ✔,✔,✔ 2 | ★,★,★ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .phpmake 2 | /vendor 3 | /composer.phar 4 | /composer.lock 5 | /metrics 6 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Join/csv_files/utf-8.csv: -------------------------------------------------------------------------------- 1 | ✔,✔,✔,✔ 2 | ★,★,★,★ 3 | 유,니,코,드 -------------------------------------------------------------------------------- /example/user.csv: -------------------------------------------------------------------------------- 1 | 1,alice,alice@example.com 2 | 2,bob,bob@example.com 3 | 3,carol,carol@eample.com -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Join/csv_files/colon-separated.csv: -------------------------------------------------------------------------------- 1 | value1:value2:value3 2 | value4:value5:value6 3 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Join/csv_files/tab-separated.csv: -------------------------------------------------------------------------------- 1 | value1 value2 value3 2 | value4 value5 value6 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Join/csv_files/sjis.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodby/csv/HEAD/src/Goodby/CSV/Import/Tests/Standard/Join/csv_files/sjis.csv -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Tests/Standard/Join/csv_files/euc-jp.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodby/csv/HEAD/src/Goodby/CSV/Export/Tests/Standard/Join/csv_files/euc-jp.csv -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Standard/Exception/StrictViolationException.php: -------------------------------------------------------------------------------- 1 | data = $data; 13 | } 14 | 15 | public function getIterator() 16 | { 17 | return new ArrayIterator($this->data); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Protocol/ExporterInterface.php: -------------------------------------------------------------------------------- 1 | query('CREATE TABLE IF NOT EXISTS user2 (id INT, `name` VARCHAR(255), email VARCHAR(255), PRIMARY KEY (`id`))'); 11 | 12 | $config = new LexerConfig(); 13 | $lexer = new Lexer($config); 14 | 15 | $interpreter = new Interpreter(); 16 | 17 | $interpreter->addObserver(function(array $columns) use ($pdo) { 18 | $stmt = $pdo->prepare('REPLACE user2 (id, name, email) VALUES (?, ?, ?)'); 19 | $stmt->execute($columns); 20 | }); 21 | 22 | $lexer->parse('user.csv', $interpreter); 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | include: 5 | - php: 5.3 6 | dist: precise 7 | - php: 5.4 8 | dist: trusty 9 | - php: 5.5 10 | dist: trusty 11 | - php: 5.6 12 | dist: trusty 13 | - php: 7.0 14 | dist: trusty 15 | - php: 7.1 16 | dist: trusty 17 | # PHP 7.2 can currently not been tested because of incompatibility with PHPUnit 3 18 | # atleast PHPUnit 7.0 would be needed which don't support 5.3 19 | # - php: 7.2 20 | # dist: trusty 21 | 22 | before_script: 23 | - ./scripts/bundle-devtools.sh . 24 | - export GOODBY_CSV_TEST_DB_HOST=127.0.0.1 25 | - export GOODBY_CSV_TEST_DB_USER=root 26 | - export GOODBY_CSV_TEST_DB_PASS="" 27 | - mysql -e 'create database goodby_csv_test' 28 | script: ./vendor/bin/phpunit --coverage-text --configuration phpunit.xml.dist 29 | -------------------------------------------------------------------------------- /example/export_from_database_via_pdo.php: -------------------------------------------------------------------------------- 1 | query('CREATE TABLE IF NOT EXISTS user (id INT, `name` VARCHAR(255), email VARCHAR(255))'); 13 | $pdo->query("INSERT INTO user VALUES(1, 'alice', 'alice@example.com')"); 14 | $pdo->query("INSERT INTO user VALUES(2, 'bob', 'bob@example.com')"); 15 | $pdo->query("INSERT INTO user VALUES(3, 'carol', 'carol@example.com')"); 16 | 17 | $config = new ExporterConfig(); 18 | $exporter = new Exporter($config); 19 | 20 | $stmt = $pdo->prepare("SELECT * FROM user"); 21 | $stmt->execute(); 22 | 23 | $exporter->export('php://output', new PdoCollection($stmt)); 24 | 25 | -------------------------------------------------------------------------------- /example/tsv-sample.php: -------------------------------------------------------------------------------- 1 | setDelimiter("\t"); 15 | $config->setFlags(\SplFileObject::READ_AHEAD | \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_CSV); 16 | $lexer = new Lexer($config); 17 | 18 | // set up interpreter 19 | $interpreter = new Interpreter(); 20 | $interpreter->addObserver(function(array $row) use (&$temperature) { 21 | $temperature[] = array( 22 | 'temperature' => $row[0], 23 | 'city' => $row[1], 24 | ); 25 | }); 26 | 27 | // parse 28 | $lexer->parse('temperature.tsv', $interpreter); 29 | 30 | var_dump($temperature); 31 | -------------------------------------------------------------------------------- /example/import_from_database_via_pdo.php: -------------------------------------------------------------------------------- 1 | query('CREATE TABLE IF NOT EXISTS user (id INT, `name` VARCHAR(255), email VARCHAR(255))'); 11 | 12 | $config = new LexerConfig(); 13 | $lexer = new Lexer($config); 14 | 15 | $interpreter = new Interpreter(); 16 | 17 | $interpreter->addObserver(function(array $columns) use ($pdo) { 18 | $checkStmt = $pdo->prepare('SELECT count(*) FROM user WHERE id = ?'); 19 | $checkStmt->execute(array(($columns[0]))); 20 | 21 | $count = $checkStmt->fetchAll()[0][0]; 22 | 23 | if ($count === 0) { 24 | $stmt = $pdo->prepare('INSERT INTO user (id, name, email) VALUES (?, ?, ?)'); 25 | $stmt->execute($columns); 26 | } 27 | }); 28 | 29 | $lexer->parse('user.csv', $interpreter); 30 | -------------------------------------------------------------------------------- /phpunit-bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.3.2", 23 | "ext-mbstring": "*" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "3.7.*", 27 | "mockery/mockery": "^0.7.2", 28 | "suin/php-expose": "^1.0", 29 | "mikey179/vfsStream": "^1.1" 30 | }, 31 | "autoload": { 32 | "psr-0": { 33 | "Goodby\\CSV": "src/" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/sample.php: -------------------------------------------------------------------------------- 1 | PDO::ERRMODE_EXCEPTION, 13 | )); 14 | $pdo->query('CREATE TABLE IF NOT EXISTS user (id INT, `name` VARCHAR(255), email VARCHAR(255))'); 15 | 16 | // Importing 17 | $config = new LexerConfig(); 18 | $lexer = new Lexer($config); 19 | $interpreter = new Interpreter(); 20 | $interpreter->addObserver(function(array $columns) use ($pdo) { 21 | $stmt = $pdo->prepare('INSERT INTO user (id, name, email) VALUES (?, ?, ?)'); 22 | $stmt->execute($columns); 23 | }); 24 | $lexer->parse('user.csv', $interpreter); 25 | 26 | // Exporting 27 | $config = new ExporterConfig(); 28 | $exporter = new Exporter($config); 29 | $exporter->export('php://output', array( 30 | array('1', 'alice', 'alice@example.com'), 31 | array('2', 'bob', 'bob@example.com'), 32 | array('3', 'carol', 'carol@example.com'), 33 | )); 34 | 35 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Protocol/LexerTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('parse')->with($path, $interpreter); 20 | 21 | $lexer->parse($path, $interpreter); 22 | } 23 | 24 | /** 25 | * @expectedException \Goodby\CSV\Import\Protocol\Exception\CsvFileNotFoundException 26 | */ 27 | public function testCsvFileNotFound() 28 | { 29 | $lexer = m::mock('\Goodby\CSV\Import\Protocol\LexerInterface'); 30 | $interpreter = m::mock('\Goodby\CSV\Import\Protocol\InterpreterInterface'); 31 | 32 | $path = 'invalid_dummy.csv'; 33 | 34 | $lexer->shouldReceive('parse') 35 | ->with($path, $interpreter) 36 | ->andThrow('Goodby\CSV\Import\Protocol\Exception\CsvFileNotFoundException') 37 | ; 38 | 39 | $lexer->parse($path, $interpreter); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Protocol/InterpreterTest.php: -------------------------------------------------------------------------------- 1 | getMock('\Goodby\CSV\Import\Protocol\InterpreterInterface'); 17 | 18 | $interpreter->expects($this->once()) 19 | ->method('interpret') 20 | ->with($this->identicalTo($line)) 21 | ; 22 | 23 | $interpreter->interpret($line); 24 | } 25 | 26 | /** 27 | * @expectedException \Goodby\CSV\Import\Protocol\Exception\InvalidLexicalException 28 | */ 29 | public function testInterpreterInterfaceWillThrownInvalidLexicalException() 30 | { 31 | $interpreter = $this->getMock('\Goodby\CSV\Import\Protocol\InterpreterInterface'); 32 | 33 | $interpreter->expects($this->once()) 34 | ->method('interpret') 35 | ->will($this->throwException(new InvalidLexicalException())) 36 | ; 37 | 38 | $line = "INVALID LEXICAL"; 39 | 40 | $interpreter->interpret($line); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/SandboxDirectoryManager.php: -------------------------------------------------------------------------------- 1 | getMock('Goodby\CSV\Export\Protocol\ExporterInterface'); 12 | $exporter->expects($this->once())->method('export'); 13 | 14 | $exporter->export('filename', array( 15 | array('ID', 'name', 'email'), 16 | array('1', 'alice', 'alice@example.com'), 17 | array('2', 'bob', 'bob@example.com'), 18 | )); 19 | } 20 | 21 | /** 22 | * @expectedException \Goodby\CSV\Export\Protocol\Exception\IOException 23 | */ 24 | public function testExportsThrowsIOException() 25 | { 26 | $exporter = $this->getMock('Goodby\CSV\Export\Protocol\ExporterInterface'); 27 | 28 | $exporter 29 | ->expects($this->once()) 30 | ->method('export') 31 | ->will($this->throwException(new IOException('Unable to write'))); 32 | 33 | $exporter->export('/path/to/file.csv', array( 34 | array('ID', 'name', 'email'), 35 | array('1', 'alice', 'alice@example.com'), 36 | array('2', 'bob', 'bob@example.com'), 37 | )); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Goodby/CSV/TestHelper/DbManager.php: -------------------------------------------------------------------------------- 1 | host = $_SERVER['GOODBY_CSV_TEST_DB_HOST']; 17 | $this->db = $_SERVER['GOODBY_CSV_TEST_DB_NAME_DEFAULT']; 18 | $this->user = $_SERVER['GOODBY_CSV_TEST_DB_USER']; 19 | $this->pass = $_SERVER['GOODBY_CSV_TEST_DB_PASS']; 20 | 21 | $dsn = 'mysql:host=' . $this->host; 22 | 23 | $this->pdo = new \PDO($dsn, $this->user, $this->pass); 24 | $stmt = $this->pdo->prepare("CREATE DATABASE " . $this->db); 25 | 26 | $stmt->execute(); 27 | } 28 | 29 | public function __destruct() 30 | { 31 | $stmt = $this->pdo->prepare("DROP DATABASE " . $this->db); 32 | $stmt->execute(); 33 | } 34 | 35 | public function getPdo() 36 | { 37 | return new \PDO($this->getDsn(), $this->user, $this->pass); 38 | } 39 | 40 | public function getDsn() 41 | { 42 | return 'mysql:dbname=' . $this->db . ';host=' . $this->host; 43 | } 44 | 45 | public function getUser() 46 | { 47 | return $this->user; 48 | } 49 | 50 | public function getPassword() 51 | { 52 | return $this->pass; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Tests/Standard/Unit/Collection/CallbackCollectionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($each[0], 'user'); 24 | $this->assertEquals($each[1], 'name' . $index); 25 | $index++; 26 | } 27 | } 28 | 29 | public function testIteratorAggregate() 30 | { 31 | 32 | $data = array(); 33 | $data[] = array('user', 'name1'); 34 | $data[] = array('user', 'name2'); 35 | $data[] = array('user', 'name3'); 36 | 37 | $iterator = new SampleAggIterator($data); 38 | 39 | $collection = new CallbackCollection($iterator, function($mixed) { 40 | return $mixed; 41 | }); 42 | 43 | $index = 1; 44 | foreach ($collection as $each) { 45 | $this->assertEquals($each[0], 'user'); 46 | $this->assertEquals($each[1], 'name' . $index); 47 | $index++; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Unit/Observer/SqlObserverTest.php: -------------------------------------------------------------------------------- 1 | addObserver(array($sqlObserver, 'notify')); 30 | 31 | $interpreter->interpret(array('123', 'test', '28', 'true', 'false', 'null', 'test"test')); 32 | 33 | $expectedSql = 'INSERT INTO test(id, name, age, flag, flag2, status, contents) VALUES(123, "test", 28, true, false, NULL, "test\"test");'; 34 | 35 | $this->assertEquals($expectedSql, file_get_contents($path)); 36 | } 37 | 38 | /** 39 | * @expectedException \InvalidArgumentException 40 | */ 41 | public function testInvalidLine() 42 | { 43 | $interpreter = new Interpreter(); 44 | 45 | $sqlObserver = new SqlObserver('test', array('id', 'name'), 'dummy'); 46 | 47 | $interpreter->addObserver(array($sqlObserver, 'notify')); 48 | 49 | $interpreter->interpret(array('123', array('test'))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Standard/Observer/SqlObserver.php: -------------------------------------------------------------------------------- 1 | table = $table; 16 | $this->columns = $columns; 17 | $this->path = $path; 18 | } 19 | 20 | public function notify($line) 21 | { 22 | $sql = $this->buildSql($line); 23 | 24 | if ($this->file === null) { 25 | $this->file = new \SplFileObject($this->path, 'a'); 26 | } 27 | 28 | $this->file->fwrite($sql); 29 | } 30 | 31 | private function buildSql($line) 32 | { 33 | $line = array_map(function($value) { 34 | $number = filter_var($value, FILTER_VALIDATE_INT); 35 | 36 | if ($number !== false) { 37 | return $number; 38 | } 39 | 40 | if (is_string($value)) { 41 | if (strtolower($value) === 'null') { 42 | return 'NULL'; 43 | } 44 | 45 | if (strtolower($value) === 'true') { 46 | return 'true'; 47 | } 48 | 49 | if (strtolower($value) === 'false') { 50 | return 'false'; 51 | } 52 | 53 | return '"' . addslashes($value) . '"'; 54 | } 55 | 56 | throw new \InvalidArgumentException('value is invalid: ' . var_export($value, 1)); 57 | }, $line); 58 | 59 | return 'INSERT INTO ' . $this->table . '(' . join(', ', $this->columns) . ')' . 60 | ' VALUES(' . join(', ', $line) . ');'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Join/CSVFiles.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | src/*/*/*/Tests 11 | 12 | 13 | 14 | 15 | ./../html/install/src 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | 35 | 36 | 37 | src 38 | 39 | 40 | 41 | src 42 | src/*/*/*/Tests 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Standard/Observer/PdoObserver.php: -------------------------------------------------------------------------------- 1 | table = $table; 18 | $this->columns = $columns; 19 | 20 | $this->dsn = $dsn; 21 | $this->options = $options; 22 | } 23 | 24 | public function notify($line) 25 | { 26 | if ($this->pdo === null) { 27 | $this->pdo = new \PDO($this->dsn, $this->options['user'], $this->options['password']); 28 | } 29 | 30 | $this->execute($line); 31 | } 32 | 33 | private function execute($line) 34 | { 35 | $line = array_map(function($value) { 36 | $number = filter_var($value, FILTER_VALIDATE_INT); 37 | 38 | if ($number !== false) { 39 | return $number; 40 | } 41 | 42 | if (is_string($value)) { 43 | if (strtolower($value) === 'null') { 44 | return 'NULL'; 45 | } 46 | 47 | if (strtolower($value) === 'true') { 48 | return 1; 49 | } 50 | 51 | if (strtolower($value) === 'false') { 52 | return 0; 53 | } 54 | 55 | return $value; 56 | } 57 | 58 | throw new \InvalidArgumentException('value is invalid: ' . var_export($value, 1)); 59 | }, $line); 60 | 61 | $prepare = array_map(function() { 62 | return '?'; 63 | }, $line); 64 | 65 | $sql = 'INSERT INTO ' . $this->table . '(' . join(', ', $this->columns) . ')' . 66 | ' VALUES(' . join(',', $prepare) . ')'; 67 | 68 | $stmt = $this->pdo->prepare($sql); 69 | $stmt->execute($line); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Tests/Standard/Join/Collection/PdoCollectionTest.php: -------------------------------------------------------------------------------- 1 | manager = new DbManager(); 20 | 21 | $pdo = $this->manager->getPdo(); 22 | 23 | $stmt = $pdo->prepare("CREATE TABLE collection_test ( id INT, name VARCHAR(32) )"); 24 | $stmt->execute(); 25 | 26 | $pdo->prepare("INSERT INTO collection_test VALUES(1, 'name')")->execute(); 27 | $pdo->prepare("INSERT INTO collection_test VALUES(2, 'name')")->execute(); 28 | $pdo->prepare("INSERT INTO collection_test VALUES(3, 'name')")->execute(); 29 | } 30 | 31 | public function tearDown() 32 | { 33 | unset($this->manager); 34 | } 35 | 36 | public function testUsage() 37 | { 38 | $pdo = $this->manager->getPdo(); 39 | 40 | $stmt = $pdo->prepare("SELECT * FROM collection_test"); 41 | $stmt->execute(); 42 | 43 | $pdoCollection = new PdoCollection($stmt); 44 | 45 | foreach ($pdoCollection as $line) { 46 | $this->assertEquals("name", $line["name"]); 47 | } 48 | } 49 | 50 | public function testUsageWithCallbackCollection() 51 | { 52 | $pdo = $this->manager->getPdo(); 53 | 54 | $stmt = $pdo->prepare("SELECT * FROM collection_test"); 55 | $stmt->execute(); 56 | 57 | $pdoCollection = new PdoCollection($stmt); 58 | 59 | $callbackCollection = new CallbackCollection($pdoCollection, function($row) { 60 | $row['test'] = 'test'; 61 | return $row; 62 | }); 63 | 64 | foreach ($callbackCollection as $line) { 65 | $this->assertEquals('test', $line['test']); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Tests/Standard/Unit/ExporterConfigTest.php: -------------------------------------------------------------------------------- 1 | assertSame(',', $config->getDelimiter()); 13 | $this->assertSame('del', $config->setDelimiter('del')->getDelimiter()); 14 | } 15 | 16 | public function testEnclosure() 17 | { 18 | $config = new ExporterConfig(); 19 | $this->assertSame('"', $config->getEnclosure()); 20 | $this->assertSame('enc', $config->setEnclosure('enc')->getEnclosure()); 21 | } 22 | 23 | public function testEscape() 24 | { 25 | $config = new ExporterConfig(); 26 | $this->assertSame('\\', $config->getEscape()); 27 | $this->assertSame('esc', $config->setEscape('esc')->getEscape()); 28 | } 29 | 30 | public function testNewline() 31 | { 32 | $config = new ExporterConfig(); 33 | $this->assertSame("\r\n", $config->getNewline()); 34 | $this->assertSame("\r", $config->setNewline("\r")->getNewline()); 35 | } 36 | 37 | public function testFromCharset() 38 | { 39 | $config = new ExporterConfig(); 40 | $this->assertSame('auto', $config->getFromCharset()); 41 | $this->assertSame('UTF-8', $config->setFromCharset('UTF-8')->getFromCharset()); 42 | } 43 | 44 | public function testToCharset() 45 | { 46 | $config = new ExporterConfig(); 47 | $this->assertSame(null, $config->getToCharset()); 48 | $this->assertSame('UTF-8', $config->setToCharset('UTF-8')->getToCharset()); 49 | } 50 | 51 | public function testColumnHeaders() 52 | { 53 | $columnHeaders = array( 54 | 'Header 1', 55 | 'Header 2', 56 | 'Header 3', 57 | ); 58 | 59 | $config = new ExporterConfig(); 60 | $this->assertSame(array(), $config->getColumnHeaders()); 61 | $this->assertSame($columnHeaders, $config->setColumnHeaders($columnHeaders)->getColumnHeaders()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Standard/Collection/PdoCollection.php: -------------------------------------------------------------------------------- 1 | stmt = $stmt; 22 | 23 | $this->rowCount = $this->stmt->rowCount(); 24 | } 25 | 26 | /** 27 | * (PHP 5 >= 5.0.0)
28 | * Return the current element 29 | * @link http://php.net/manual/en/iterator.current.php 30 | * @return mixed Can return any type. 31 | */ 32 | public function current() 33 | { 34 | return $this->stmt->fetch(PDO::FETCH_ASSOC); 35 | } 36 | 37 | /** 38 | * (PHP 5 >= 5.0.0)
39 | * Move forward to next element 40 | * @link http://php.net/manual/en/iterator.next.php 41 | * @return void Any returned value is ignored. 42 | */ 43 | public function next() 44 | { 45 | $this->current++; 46 | } 47 | 48 | /** 49 | * (PHP 5 >= 5.0.0)
50 | * Return the key of the current element 51 | * @link http://php.net/manual/en/iterator.key.php 52 | * @return mixed scalar on success, or null on failure. 53 | */ 54 | public function key() 55 | { 56 | $this->current; 57 | } 58 | 59 | /** 60 | * (PHP 5 >= 5.0.0)
61 | * Checks if current position is valid 62 | * @link http://php.net/manual/en/iterator.valid.php 63 | * @return boolean The return value will be casted to boolean and then evaluated. 64 | * Returns true on success or false on failure. 65 | */ 66 | public function valid() 67 | { 68 | return ($this->rowCount > $this->current); 69 | } 70 | 71 | /** 72 | * (PHP 5 >= 5.0.0)
73 | * Rewind the Iterator to the first element 74 | * @link http://php.net/manual/en/iterator.rewind.php 75 | * @return void Any returned value is ignored. 76 | */ 77 | public function rewind() 78 | { 79 | $this->stmt->execute(); 80 | $this->current = 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Unit/LexerConfigTest.php: -------------------------------------------------------------------------------- 1 | assertSame(',', $config->getDelimiter()); 14 | $config->setDelimiter('del'); 15 | $this->assertSame('del', $config->getDelimiter()); 16 | } 17 | 18 | public function testEnclosure() 19 | { 20 | $config = new LexerConfig(); 21 | $this->assertSame('"', $config->getEnclosure()); 22 | $this->assertSame('enc', $config->setEnclosure('enc')->getEnclosure()); 23 | } 24 | 25 | public function testEscape() 26 | { 27 | $config = new LexerConfig(); 28 | $this->assertSame('\\', $config->getEscape()); 29 | $this->assertSame('esc', $config->setEscape('esc')->getEscape()); 30 | } 31 | 32 | public function testFromCharset() 33 | { 34 | $config = new LexerConfig(); 35 | $this->assertSame(null, $config->getFromCharset()); 36 | $this->assertSame('UTF-8', $config->setFromCharset('UTF-8')->getFromCharset()); 37 | } 38 | 39 | public function testToCharset() 40 | { 41 | $config = new LexerConfig(); 42 | $this->assertSame(null, $config->getToCharset()); 43 | $this->assertSame('UTF-8', $config->setToCharset('UTF-8')->getToCharset()); 44 | } 45 | 46 | public function testFlags() 47 | { 48 | $config = new LexerConfig(); 49 | $this->assertSame(SplFileObject::READ_CSV, $config->getFlags()); 50 | $config->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::READ_CSV); 51 | $flags = (SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::READ_CSV); 52 | $this->assertSame($flags, $config->getFlags()); 53 | } 54 | 55 | public function testIgnoreHeaderLine() 56 | { 57 | $config = new LexerConfig(); 58 | $this->assertSame(false, $config->getIgnoreHeaderLine()); 59 | $this->assertSame(true, $config->setIgnoreHeaderLine(true)->getIgnoreHeaderLine()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Unit/StreamFilter/ConvertMbstringEncodingTest.php: -------------------------------------------------------------------------------- 1 | internalEncodingBackup = mb_internal_encoding(); 15 | } 16 | 17 | public function tearDown() 18 | { 19 | mb_internal_encoding($this->internalEncodingBackup); 20 | } 21 | 22 | public function testGetFilterName() 23 | { 24 | $this->assertSame('convert.mbstring.encoding.*', ConvertMbstringEncoding::getFilterName()); 25 | } 26 | 27 | public function testOneParameter() 28 | { 29 | $filterString = 'convert.mbstring.encoding.EUC-JP'; 30 | mb_internal_encoding('UTF-7'); 31 | $filter = new ConvertMbstringEncoding(); 32 | $filter->filtername = $filterString; 33 | $filter->onCreate(); 34 | $this->assertAttributeSame('EUC-JP', 'fromCharset', $filter); 35 | $this->assertAttributeSame('UTF-7', 'toCharset', $filter); 36 | } 37 | 38 | public function testTwoParameters() 39 | { 40 | $filterString = 'convert.mbstring.encoding.SJIS-win:UTF-8'; 41 | mb_internal_encoding('UTF-7'); 42 | $filter = new ConvertMbstringEncoding(); 43 | $filter->filtername = $filterString; 44 | $filter->onCreate(); 45 | $this->assertAttributeSame('SJIS-win', 'fromCharset', $filter); 46 | $this->assertAttributeSame('UTF-8', 'toCharset', $filter); 47 | } 48 | 49 | public function test_when_invalid_parameter_given_it_returns_false() 50 | { 51 | $filterString = 'convert.mbstring.encoding.@#$#!%^^'; 52 | $filter = new ConvertMbstringEncoding(); 53 | $filter->filtername = $filterString; 54 | $this->assertFalse($filter->onCreate()); 55 | } 56 | 57 | public function test_register_filter() 58 | { 59 | ConvertMbstringEncoding::register(); 60 | $filterName = ConvertMbstringEncoding::getFilterName(); 61 | $registeredFilters = stream_get_filters(); 62 | $this->assertTrue(in_array($filterName, $registeredFilters)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Standard/Lexer.php: -------------------------------------------------------------------------------- 1 | config = $config; 28 | ConvertMbstringEncoding::register(); 29 | } 30 | 31 | /** 32 | * {@inherit} 33 | */ 34 | public function parse($filename, InterpreterInterface $interpreter) 35 | { 36 | ini_set('auto_detect_line_endings', true); // For mac's office excel csv 37 | 38 | $delimiter = $this->config->getDelimiter(); 39 | $enclosure = $this->config->getEnclosure(); 40 | $escape = $this->config->getEscape(); 41 | $fromCharset = $this->config->getFromCharset(); 42 | $toCharset = $this->config->getToCharset(); 43 | $flags = $this->config->getFlags(); 44 | $ignoreHeader = $this->config->getIgnoreHeaderLine(); 45 | 46 | if ( $fromCharset === null ) { 47 | $url = $filename; 48 | } else { 49 | $url = ConvertMbstringEncoding::getFilterURL($filename, $fromCharset, $toCharset); 50 | } 51 | 52 | $csv = new SplFileObject($url); 53 | $csv->setCsvControl($delimiter, $enclosure, $escape); 54 | $csv->setFlags($flags); 55 | 56 | $originalLocale = setlocale(LC_ALL, '0'); // Backup current locale 57 | setlocale(LC_ALL, 'en_US.UTF-8'); 58 | 59 | foreach ( $csv as $lineNumber => $line ) { 60 | if ($ignoreHeader && $lineNumber == 0 || (count($line) === 1 && trim($line[0]) === '')) { 61 | continue; 62 | } 63 | $interpreter->interpret($line); 64 | } 65 | 66 | parse_str(str_replace(';', '&', $originalLocale), $locale_array); 67 | setlocale(LC_ALL, $locale_array); // Reset locale 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Standard/CsvFileObject.php: -------------------------------------------------------------------------------- 1 | newline = $newline; 31 | } 32 | 33 | /** 34 | * Set csv filter 35 | * @param callable $filter 36 | */ 37 | public function setCsvFilter($filter) 38 | { 39 | $this->csvFilter = $filter; 40 | } 41 | 42 | /** 43 | * Write a field array as a CSV line 44 | * @param array $fields 45 | * @param string $delimiter 46 | * @param string $enclosure 47 | * @param useless $escape THIS PARAM IS UNSED, BUT REQUIRED EXISTS, see https://bugs.php.net/bug.php?id=68479 and https://github.com/goodby/csv/issues/56 48 | * @return int|void 49 | */ 50 | public function fputcsv($fields, $delimiter = null, $enclosure = null, $escape = null) 51 | { 52 | // Temporary output a line to memory to get line as string 53 | $fp = fopen('php://temp', 'w+'); 54 | $arguments = func_get_args(); 55 | array_unshift($arguments, $fp); 56 | call_user_func_array('fputcsv', $arguments); 57 | rewind($fp); 58 | 59 | $line = ''; 60 | 61 | while ( feof($fp) === false ) { 62 | $line .= fgets($fp); 63 | } 64 | 65 | fclose($fp); 66 | 67 | /** 68 | * Because the php_fputcsv() implementation in PHP´s source code 69 | * has a hardcoded "\n", this method replaces the last LF code 70 | * with what the client code wishes. 71 | */ 72 | $line = rtrim($line, "\n"). $this->newline; 73 | 74 | // if the enclosure was '' | false 75 | if (empty($enclosure)) { 76 | $line = str_replace("\0", '', $line); 77 | } 78 | 79 | if ( is_callable($this->csvFilter) ) { 80 | $line = call_user_func($this->csvFilter, $line); 81 | } 82 | 83 | return $this->fwrite($line); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Standard/Collection/CallbackCollection.php: -------------------------------------------------------------------------------- 1 | callable = $callable; 16 | 17 | if (!is_callable($callable)) { 18 | throw new \InvalidArgumentException('the second argument must be callable'); 19 | } 20 | 21 | if (is_array($data)) { 22 | $ao = new \ArrayObject($data); 23 | $this->data = $ao->getIterator(); 24 | } elseif ($data instanceof Iterator) { 25 | $this->data = $data; 26 | } elseif ($data instanceof IteratorAggregate) { 27 | $this->data = $data->getIterator(); 28 | } else { 29 | throw new \InvalidArgumentException('data must be an array or an Iterator/IteratorAggregate'); 30 | } 31 | } 32 | 33 | /** 34 | * (PHP 5 >= 5.0.0)
35 | * Return the current element 36 | * @link http://php.net/manual/en/iterator.current.php 37 | * @return mixed Can return any type. 38 | */ 39 | public function current() 40 | { 41 | return call_user_func($this->callable, $this->data->current()); 42 | } 43 | 44 | /** 45 | * (PHP 5 >= 5.0.0)
46 | * Move forward to next element 47 | * @link http://php.net/manual/en/iterator.next.php 48 | * @return void Any returned value is ignored. 49 | */ 50 | public function next() 51 | { 52 | $this->data->next(); 53 | } 54 | 55 | /** 56 | * (PHP 5 >= 5.0.0)
57 | * Return the key of the current element 58 | * @link http://php.net/manual/en/iterator.key.php 59 | * @return mixed scalar on success, or null on failure. 60 | */ 61 | public function key() 62 | { 63 | return $this->data->key(); 64 | } 65 | 66 | /** 67 | * (PHP 5 >= 5.0.0)
68 | * Checks if current position is valid 69 | * @link http://php.net/manual/en/iterator.valid.php 70 | * @return boolean The return value will be casted to boolean and then evaluated. 71 | * Returns true on success or false on failure. 72 | */ 73 | public function valid() 74 | { 75 | return $this->data->valid(); 76 | } 77 | 78 | /** 79 | * (PHP 5 >= 5.0.0)
80 | * Rewind the Iterator to the first element 81 | * @link http://php.net/manual/en/iterator.rewind.php 82 | * @return void Any returned value is ignored. 83 | */ 84 | public function rewind() 85 | { 86 | $this->data->rewind(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Join/Observer/PdoObserverTest.php: -------------------------------------------------------------------------------- 1 | manager = new DbManager(); 25 | 26 | $pdo = $this->manager->getPdo(); 27 | 28 | $stmt = $pdo->prepare("CREATE TABLE test (id INT, name VARCHAR(32), age INT, flag TINYINT, flag2 TINYINT, status VARCHAR(32), contents TEXT)"); 29 | $stmt->execute(); 30 | } 31 | 32 | public function tearDown() 33 | { 34 | unset($this->manager); 35 | } 36 | 37 | public function testUsage() 38 | { 39 | $interpreter = new Interpreter(); 40 | 41 | $table = 'test'; 42 | 43 | $dsn = $this->manager->getDsn(); 44 | $options = array('user' => $this->manager->getUser(), 'password' => $this->manager->getPassword()); 45 | 46 | $sqlObserver = new PdoObserver($table, array('id', 'name', 'age', 'flag', 'flag2', 'status', 'contents'), $dsn, $options); 47 | 48 | $interpreter->addObserver(array($sqlObserver, 'notify')); 49 | 50 | $interpreter->interpret(array('123', 'test', '28', 'true', 'false', 'null', 'test"test')); 51 | 52 | $pdo = $this->manager->getPdo(); 53 | 54 | $stmt = $pdo->prepare("SELECT * FROM " . $table); 55 | $stmt->execute(); 56 | 57 | $result = $stmt->fetch(); 58 | 59 | $this->assertEquals(123, $result[0]); 60 | $this->assertEquals('test', $result[1]); 61 | $this->assertEquals(28, $result[2]); 62 | $this->assertEquals(1, $result[3]); 63 | $this->assertEquals(0, $result[4]); 64 | $this->assertEquals('NULL', $result[5]); 65 | $this->assertEquals('test"test', $result[6]); 66 | } 67 | 68 | /** 69 | * @expectedException \InvalidArgumentException 70 | * @expectedExceptionMessage value is invalid: array 71 | */ 72 | public function testInvalidLine() 73 | { 74 | $interpreter = new Interpreter(); 75 | 76 | $table = 'test'; 77 | 78 | $options = array('user' => $this->manager->getUser(), 'password' => $this->manager->getPassword()); 79 | 80 | $sqlObserver = new PdoObserver($table, array('id', 'name'), $this->manager->getDsn(), $options); 81 | 82 | $interpreter->addObserver(array($sqlObserver, 'notify')); 83 | 84 | $interpreter->interpret(array('123', array('test', 'test'))); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Tests/Standard/Join/UsageTest.php: -------------------------------------------------------------------------------- 1 | root = vfsStream::setup('output'); 31 | 32 | $this->manager = new DbManager(); 33 | 34 | $pdo = $this->manager->getPdo(); 35 | 36 | $stmt = $pdo->prepare("CREATE TABLE collection_test ( id INT, name VARCHAR(32) )"); 37 | $stmt->execute(); 38 | 39 | $pdo->prepare("INSERT INTO collection_test VALUES(1, 'name')")->execute(); 40 | $pdo->prepare("INSERT INTO collection_test VALUES(2, 'name')")->execute(); 41 | $pdo->prepare("INSERT INTO collection_test VALUES(3, 'name')")->execute(); 42 | } 43 | 44 | public function tearDown() 45 | { 46 | unset($this->manager); 47 | } 48 | 49 | public function testUsage() 50 | { 51 | $pdo = $this->manager->getPdo(); 52 | 53 | $stmt = $pdo->prepare("SELECT * FROM collection_test"); 54 | $stmt->execute(); 55 | 56 | $this->assertFileNotExists('vfs://output/data.csv'); 57 | 58 | $collection = new PdoCollection($stmt); 59 | 60 | $config = new ExporterConfig(); 61 | $exporter = new Exporter($config); 62 | $exporter->export('vfs://output/data.csv', $collection); 63 | 64 | $expectedContents = "1,name\r\n"; 65 | $expectedContents .= "2,name\r\n"; 66 | $expectedContents .= "3,name\r\n"; 67 | 68 | $this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv')); 69 | } 70 | 71 | public function testUsageWithCallbackCollection() 72 | { 73 | $this->assertFileNotExists('vfs://output/data.csv'); 74 | 75 | $data = array(); 76 | $data[] = array(1, 'name1'); 77 | $data[] = array(2, 'name2'); 78 | $data[] = array(3, 'name3'); 79 | 80 | $collection = new CallbackCollection($data, function($row) { 81 | $row[1] = $row[1] . '!'; 82 | return $row; 83 | }); 84 | 85 | $config = new ExporterConfig(); 86 | $exporter = new Exporter($config); 87 | $exporter->export('vfs://output/data.csv', $collection); 88 | 89 | $expectedContents = "1,name1!\r\n"; 90 | $expectedContents .= "2,name2!\r\n"; 91 | $expectedContents .= "3,name3!\r\n"; 92 | 93 | $this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv')); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Standard/Interpreter.php: -------------------------------------------------------------------------------- 1 | checkRowConsistency($line); 39 | 40 | if (!is_array($line)) { 41 | throw new InvalidLexicalException('line is must be array'); 42 | } 43 | 44 | $this->notify($line); 45 | } 46 | 47 | public function unstrict() 48 | { 49 | $this->strict = false; 50 | } 51 | 52 | /** 53 | * add observer 54 | * 55 | * @param callable $observer 56 | */ 57 | public function addObserver($observer) 58 | { 59 | $this->checkCallable($observer); 60 | 61 | $this->observers[] = $observer; 62 | } 63 | 64 | /** 65 | * notify to observers 66 | * 67 | * @param $line 68 | */ 69 | private function notify($line) 70 | { 71 | $observers = $this->observers; 72 | 73 | foreach ($observers as $observer) { 74 | $this->delegate($observer, $line); 75 | } 76 | } 77 | 78 | /** 79 | * delegate to observer 80 | * 81 | * @param $observer 82 | * @param $line 83 | */ 84 | private function delegate($observer, $line) 85 | { 86 | call_user_func($observer, $line); 87 | } 88 | 89 | /** 90 | * check observer is callable 91 | * 92 | * @param $observer 93 | * @throws \InvalidArgumentException 94 | */ 95 | private function checkCallable($observer) 96 | { 97 | if (!is_callable($observer)) { 98 | throw new \InvalidArgumentException('observer must be callable'); 99 | } 100 | } 101 | 102 | private function checkRowConsistency($line) 103 | { 104 | if (!$this->strict) { 105 | return; 106 | } 107 | 108 | $current = count($line); 109 | 110 | if ($this->rowConsistency === null) { 111 | $this->rowConsistency = $current; 112 | } 113 | 114 | if ($current !== $this->rowConsistency) { 115 | throw new StrictViolationException(sprintf('Column size should be %u, but %u columns given', $this->rowConsistency, $current)); 116 | } 117 | 118 | $this->rowConsistency = $current; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Standard/Exporter.php: -------------------------------------------------------------------------------- 1 | config = $config; 36 | } 37 | 38 | /** 39 | * Disable strict mode 40 | */ 41 | public function unstrict() 42 | { 43 | $this->strict = false; 44 | } 45 | 46 | /** 47 | * {@inherit} 48 | * @throws StrictViolationException 49 | */ 50 | public function export($filename, $rows) 51 | { 52 | $delimiter = $this->config->getDelimiter(); 53 | $enclosure = $this->config->getEnclosure(); 54 | $enclosure = empty($enclosure) ? "\0" : $enclosure; 55 | $newline = $this->config->getNewline(); 56 | $fromCharset = $this->config->getFromCharset(); 57 | $toCharset = $this->config->getToCharset(); 58 | $fileMode = $this->config->getFileMode(); 59 | $columnHeaders = $this->config->getColumnHeaders(); 60 | 61 | try { 62 | $csv = new CsvFileObject($filename, $fileMode); 63 | } catch ( \Exception $e ) { 64 | throw new IOException($e->getMessage(), null, $e); 65 | } 66 | 67 | $csv->setNewline($newline); 68 | 69 | if ( $toCharset ) { 70 | $csv->setCsvFilter(function($line) use($toCharset, $fromCharset) { 71 | return mb_convert_encoding($line, $toCharset, $fromCharset); 72 | }); 73 | } 74 | 75 | if (count($columnHeaders) > 0) { 76 | $this->checkRowConsistency($columnHeaders); 77 | $csv->fputcsv($columnHeaders, $delimiter, $enclosure); 78 | } 79 | 80 | foreach ( $rows as $row ) { 81 | $this->checkRowConsistency($row); 82 | $csv->fputcsv($row, $delimiter, $enclosure); 83 | } 84 | $csv->fflush(); 85 | } 86 | 87 | /** 88 | * Check if the column count is consistent with comparing other rows 89 | * @param array|\Countable $row 90 | * @throws Exception\StrictViolationException 91 | */ 92 | private function checkRowConsistency($row) 93 | { 94 | if ( $this->strict === false ) { 95 | return; 96 | } 97 | 98 | $current = count($row); 99 | 100 | if ( $this->rowConsistency === null ) { 101 | $this->rowConsistency = $current; 102 | } 103 | 104 | if ( $current !== $this->rowConsistency ) { 105 | throw new StrictViolationException(); 106 | } 107 | 108 | $this->rowConsistency = $current; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Standard/StreamFilter/ConvertMbstringEncoding.php: -------------------------------------------------------------------------------- 1 | filtername, self::FILTER_NAMESPACE) !== 0 ) { 78 | return false; 79 | } 80 | 81 | $parameterString = substr($this->filtername, strlen(self::FILTER_NAMESPACE)); 82 | 83 | if ( ! preg_match('/^(?P[-\w]+)(:(?P[-\w]+))?$/', $parameterString, $matches) ) { 84 | return false; 85 | } 86 | 87 | $this->fromCharset = isset($matches['from']) ? $matches['from'] : 'auto'; 88 | $this->toCharset = isset($matches['to']) ? $matches['to'] : mb_internal_encoding(); 89 | 90 | return true; 91 | } 92 | 93 | /** 94 | * @param string $in 95 | * @param string $out 96 | * @param string $consumed 97 | * @param $closing 98 | * @return int 99 | */ 100 | public function filter($in, $out, &$consumed, $closing) 101 | { 102 | while ( $bucket = stream_bucket_make_writeable($in) ) { 103 | $bucket->data = mb_convert_encoding($bucket->data, $this->toCharset, $this->fromCharset); 104 | $consumed += $bucket->datalen; 105 | stream_bucket_append($out, $bucket); 106 | } 107 | 108 | return PSFS_PASS_ON; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Unit/InterpreterTest.php: -------------------------------------------------------------------------------- 1 | expectedLine = null; 20 | } 21 | 22 | /** 23 | * @requires PHP 5.4 24 | */ 25 | public function testStandardInterpreterWithClosure() 26 | { 27 | $this->expectedLine = array('test', 'test', 'test'); 28 | 29 | $interpreter = new Interpreter(); 30 | $interpreter->addObserver(function($line) { 31 | $this->assertEquals($this->expectedLine, $line); 32 | }); 33 | 34 | $interpreter->interpret($this->expectedLine); 35 | } 36 | 37 | public function testStandardInterpreterWithObject() 38 | { 39 | $this->expectedLine = array('test', 'test', 'test'); 40 | 41 | $object = m::mock('stdClass'); 42 | $object->shouldReceive('callback')->with($this->expectedLine)->once(); 43 | 44 | $interpreter = new Interpreter(); 45 | $interpreter->addObserver(array($object, 'callback')); 46 | 47 | $interpreter->interpret($this->expectedLine); 48 | } 49 | 50 | /** 51 | * @expectedException \Goodby\CSV\Import\Standard\Exception\StrictViolationException 52 | */ 53 | public function testInconsistentColumns() 54 | { 55 | $lines[] = array('test', 'test', 'test'); 56 | $lines[] = array('test', 'test'); 57 | 58 | $interpreter = new Interpreter(); 59 | 60 | foreach ($lines as $line) { 61 | $interpreter->interpret($line); 62 | } 63 | } 64 | 65 | /** 66 | * @expectedException \Goodby\CSV\Import\Standard\Exception\StrictViolationException 67 | */ 68 | public function testInconsistentColumnsLowToHigh() 69 | { 70 | $lines[] = array('test', 'test'); 71 | $lines[] = array('test', 'test', 'test'); 72 | 73 | $interpreter = new Interpreter(); 74 | 75 | foreach ($lines as $line) { 76 | $interpreter->interpret($line); 77 | } 78 | } 79 | 80 | public function testConsistentColumns() 81 | { 82 | $lines[] = array('test', 'test', 'test'); 83 | $lines[] = array('test', 'test', 'test'); 84 | 85 | $interpreter = new Interpreter(); 86 | 87 | foreach ($lines as $line) { 88 | $interpreter->interpret($line); 89 | } 90 | } 91 | 92 | /** 93 | * use un-strict won't throw exception with inconsistent columns 94 | * 95 | */ 96 | public function testInconsistentColumnsWithUnStrict() 97 | { 98 | $lines[] = array('test', 'test', 'test'); 99 | $lines[] = array('test', 'test'); 100 | 101 | $interpreter = new Interpreter(); 102 | $interpreter->unstrict(); 103 | 104 | foreach ($lines as $line) { 105 | $interpreter->interpret($line); 106 | } 107 | } 108 | 109 | /** 110 | * @expectedException \Goodby\CSV\Import\Protocol\Exception\InvalidLexicalException 111 | */ 112 | public function testStandardInterpreterWithInvalidLexical() 113 | { 114 | $this->expectedLine = ''; 115 | 116 | $interpreter = new Interpreter(); 117 | 118 | $interpreter->interpret($this->expectedLine); 119 | } 120 | 121 | /** 122 | * @expectedException \InvalidArgumentException 123 | */ 124 | public function testInvalidCallable() 125 | { 126 | $interpreter = new Interpreter(); 127 | 128 | $interpreter->addObserver('dummy'); 129 | 130 | $interpreter->interpret($this->expectedLine); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Standard/LexerConfig.php: -------------------------------------------------------------------------------- 1 | delimiter = $delimiter; 55 | return $this; 56 | } 57 | 58 | /** 59 | * Return delimiter 60 | * @return string 61 | */ 62 | public function getDelimiter() 63 | { 64 | return $this->delimiter; 65 | } 66 | 67 | /** 68 | * Set enclosure 69 | * @param string $enclosure 70 | * @return LexerConfig 71 | */ 72 | public function setEnclosure($enclosure) 73 | { 74 | $this->enclosure = $enclosure; 75 | return $this; 76 | } 77 | 78 | /** 79 | * Return enclosure 80 | * @return string 81 | */ 82 | public function getEnclosure() 83 | { 84 | return $this->enclosure; 85 | } 86 | 87 | /** 88 | * Set escape 89 | * @param string $escape 90 | * @return LexerConfig 91 | */ 92 | public function setEscape($escape) 93 | { 94 | $this->escape = $escape; 95 | return $this; 96 | } 97 | 98 | /** 99 | * Return escape 100 | * @return string 101 | */ 102 | public function getEscape() 103 | { 104 | return $this->escape; 105 | } 106 | 107 | /** 108 | * Set from-character set 109 | * @param string $fromCharset 110 | * @return LexerConfig 111 | */ 112 | public function setFromCharset($fromCharset) 113 | { 114 | $this->fromCharset = $fromCharset; 115 | return $this; 116 | } 117 | 118 | /** 119 | * Return from-character set 120 | * @return string 121 | */ 122 | public function getFromCharset() 123 | { 124 | return $this->fromCharset; 125 | } 126 | 127 | /** 128 | * Set to-character set 129 | * @param string $toCharset 130 | * @return LexerConfig 131 | */ 132 | public function setToCharset($toCharset) 133 | { 134 | $this->toCharset = $toCharset; 135 | return $this; 136 | } 137 | 138 | /** 139 | * Return to-character set 140 | * @return string 141 | */ 142 | public function getToCharset() 143 | { 144 | return $this->toCharset; 145 | } 146 | 147 | /** 148 | * Set flags 149 | * @param integer $flags Bit mask of the flags to set. See SplFileObject constants for the available flags. 150 | * @return LexerConfig 151 | * @see http://php.net/manual/en/class.splfileobject.php#splfileobject.constants 152 | */ 153 | public function setFlags($flags) 154 | { 155 | $this->flags = $flags; 156 | return $this; 157 | } 158 | 159 | /** 160 | * Return flags 161 | * @return integer 162 | */ 163 | public function getFlags() 164 | { 165 | return $this->flags; 166 | } 167 | 168 | /** 169 | * @param $ignoreHeaderLine 170 | * @return $this 171 | */ 172 | public function setIgnoreHeaderLine($ignoreHeaderLine) 173 | { 174 | $this->ignoreHeaderLine = $ignoreHeaderLine; 175 | return $this; 176 | } 177 | 178 | /** 179 | * @return boolean 180 | */ 181 | public function getIgnoreHeaderLine() 182 | { 183 | return $this->ignoreHeaderLine; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Standard/ExporterConfig.php: -------------------------------------------------------------------------------- 1 | delimiter = $delimiter; 66 | return $this; 67 | } 68 | 69 | /** 70 | * Return delimiter 71 | * @return string 72 | */ 73 | public function getDelimiter() 74 | { 75 | return $this->delimiter; 76 | } 77 | 78 | /** 79 | * Set enclosure 80 | * @param string $enclosure 81 | * @return ExporterConfig 82 | */ 83 | public function setEnclosure($enclosure) 84 | { 85 | $this->enclosure = $enclosure; 86 | return $this; 87 | } 88 | 89 | /** 90 | * Return enclosure 91 | * @return string 92 | */ 93 | public function getEnclosure() 94 | { 95 | return $this->enclosure; 96 | } 97 | 98 | /** 99 | * Set escape 100 | * @param string $escape 101 | * @return ExporterConfig 102 | */ 103 | public function setEscape($escape) 104 | { 105 | $this->escape = $escape; 106 | return $this; 107 | } 108 | 109 | /** 110 | * Return escape 111 | * @return string 112 | */ 113 | public function getEscape() 114 | { 115 | return $this->escape; 116 | } 117 | 118 | /** 119 | * Set newline 120 | * @param string $newline 121 | * @return ExporterConfig 122 | */ 123 | public function setNewline($newline) 124 | { 125 | $this->newline = $newline; 126 | return $this; 127 | } 128 | 129 | /** 130 | * Return newline 131 | * @return string 132 | */ 133 | public function getNewline() 134 | { 135 | return $this->newline; 136 | } 137 | 138 | /** 139 | * Set from-character set 140 | * @param string $fromCharset 141 | * @return ExporterConfig 142 | */ 143 | public function setFromCharset($fromCharset) 144 | { 145 | $this->fromCharset = $fromCharset; 146 | return $this; 147 | } 148 | 149 | /** 150 | * Return from-character set 151 | * @return string 152 | */ 153 | public function getFromCharset() 154 | { 155 | return $this->fromCharset; 156 | } 157 | 158 | /** 159 | * Set to-character set 160 | * @param string $toCharset 161 | * @return ExporterConfig 162 | */ 163 | public function setToCharset($toCharset) 164 | { 165 | $this->toCharset = $toCharset; 166 | return $this; 167 | } 168 | 169 | /** 170 | * Return to-character set 171 | * @return string 172 | */ 173 | public function getToCharset() 174 | { 175 | return $this->toCharset; 176 | } 177 | 178 | /** 179 | * Set file mode 180 | * @param string $fileMode 181 | * @return ExporterConfig 182 | */ 183 | public function setFileMode($fileMode) 184 | { 185 | $this->fileMode = $fileMode; 186 | return $this; 187 | } 188 | 189 | /** 190 | * Return file mode 191 | * @return string 192 | */ 193 | public function getFileMode() 194 | { 195 | return $this->fileMode; 196 | } 197 | 198 | /** 199 | * Set the column headers. 200 | * @param array $columnHeaders 201 | * @return ExporterConfig 202 | */ 203 | public function setColumnHeaders(array $columnHeaders) 204 | { 205 | $this->columnHeaders = $columnHeaders; 206 | 207 | return $this; 208 | } 209 | 210 | /** 211 | * Get the column headers. 212 | * @return array 213 | */ 214 | public function getColumnHeaders() 215 | { 216 | return $this->columnHeaders; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Import/Tests/Standard/Join/LexerTest.php: -------------------------------------------------------------------------------- 1 | getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret')); 23 | $interpreter->expects($this->at(0))->method('interpret')->with($lines[0]); 24 | $interpreter->expects($this->at(1))->method('interpret')->with($lines[1]); 25 | $interpreter->expects($this->at(2))->method('interpret')->with($lines[2]); 26 | $interpreter->expects($this->at(3))->method('interpret')->with($lines[3]); 27 | 28 | $config = new LexerConfig(); 29 | $config->setToCharset('UTF-8')->setFromCharset('SJIS-win'); 30 | $lexer = new Lexer($config); 31 | $lexer->parse($shiftJisCsv, $interpreter); 32 | } 33 | 34 | public function test_mac_excel_csv() 35 | { 36 | $csv = CSVFiles::getMacExcelCsv(); 37 | $lines = CSVFiles::getMacExcelLines(); 38 | 39 | $interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret')); 40 | $interpreter->expects($this->at(0))->method('interpret')->with($lines[0]); 41 | $interpreter->expects($this->at(1))->method('interpret')->with($lines[1]); 42 | 43 | $config = new LexerConfig(); 44 | $lexer = new Lexer($config); 45 | $lexer->parse($csv, $interpreter); 46 | } 47 | 48 | public function test_tab_separated_csv() 49 | { 50 | $csv = CSVFiles::getTabSeparatedCsv(); 51 | $lines = CSVFiles::getTabSeparatedLines(); 52 | 53 | $interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret')); 54 | $interpreter->expects($this->at(0))->method('interpret')->with($lines[0]); 55 | $interpreter->expects($this->at(1))->method('interpret')->with($lines[1]); 56 | 57 | $config = new LexerConfig(); 58 | $config->setDelimiter("\t"); 59 | $lexer = new Lexer($config); 60 | $lexer->parse($csv, $interpreter); 61 | } 62 | 63 | public function test_colon_separated_csv() 64 | { 65 | $csv = CSVFiles::getColonSeparatedCsv(); 66 | $lines = CSVFiles::getColonSeparatedLines(); 67 | 68 | $interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret')); 69 | $interpreter->expects($this->at(0))->method('interpret')->with($lines[0]); 70 | $interpreter->expects($this->at(1))->method('interpret')->with($lines[1]); 71 | 72 | $config = new LexerConfig(); 73 | $config->setDelimiter(':'); 74 | $lexer = new Lexer($config); 75 | $lexer->parse($csv, $interpreter); 76 | } 77 | 78 | public function test_utf8_csv() 79 | { 80 | $csv = CSVFiles::getUtf8Csv(); 81 | $lines = CSVFiles::getUtf8Lines(); 82 | 83 | $interpreter = $this->getMock('Goodby\CSV\Import\Standard\Interpreter', array('interpret')); 84 | $interpreter->expects($this->at(0))->method('interpret')->with($lines[0]); 85 | $interpreter->expects($this->at(1))->method('interpret')->with($lines[1]); 86 | 87 | $config = new LexerConfig(); 88 | $lexer = new Lexer($config); 89 | $lexer->parse($csv, $interpreter); 90 | } 91 | 92 | /** 93 | * When import CSV file with data in Japanese (2 bytes character), 94 | * data imported to database with error encoding 95 | * @link https://github.com/goodby/csv/issues/5 96 | */ 97 | public function test_issue_5() 98 | { 99 | $csvFilename = CSVFiles::getIssue5CSV(); 100 | 101 | $csvContents = array(); 102 | 103 | $config = new LexerConfig(); 104 | $config 105 | ->setToCharset('UTF-8') 106 | ->setFromCharset('UTF-8'); 107 | $lexer = new Lexer($config); 108 | $interpreter = new Interpreter(); 109 | $interpreter->addObserver(function(array $columns) use (&$csvContents) { 110 | $csvContents[] = $columns; 111 | }); 112 | 113 | $lexer->parse($csvFilename, $interpreter); 114 | 115 | $this->assertSame(array( 116 | array("ID", "NAME", "MAKER"), 117 | array("1", "スティック型クリーナ", "alice_updated@example.com"), 118 | array("2", "bob", "bob@example.com"), 119 | array("14", "スティック型クリーナ", "tho@eample.com"), 120 | array("16", "スティック型", "carot@eample.com"), 121 | ), $csvContents); 122 | } 123 | 124 | public function test_ignore_header() 125 | { 126 | $csvFilename = CSVFiles::getIssue5CSV(); 127 | 128 | $config = new LexerConfig(); 129 | $config 130 | ->setIgnoreHeaderLine(true) 131 | ->setToCharset('UTF-8') 132 | ->setFromCharset('UTF-8'); 133 | 134 | $lexer = new Lexer($config); 135 | 136 | $interpreter = new Interpreter(); 137 | $interpreter->addObserver(function(array $columns) use (&$csvContents) { 138 | $csvContents[] = $columns; 139 | }); 140 | 141 | $lexer->parse($csvFilename, $interpreter); 142 | $this->assertSame(array( 143 | array("1", "スティック型クリーナ", "alice_updated@example.com"), 144 | array("2", "bob", "bob@example.com"), 145 | array("14", "スティック型クリーナ", "tho@eample.com"), 146 | array("16", "スティック型", "carot@eample.com"), 147 | ), $csvContents); 148 | } 149 | 150 | public function test_instantiation_without_config() 151 | { 152 | $lexer = new Lexer(); 153 | 154 | $this->assertInstanceOf('Goodby\CSV\Import\Standard\Lexer', $lexer); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Goodby/CSV/Export/Tests/Standard/Join/ExporterTest.php: -------------------------------------------------------------------------------- 1 | root = vfsStream::setup('output'); 26 | } 27 | 28 | public function testExport() 29 | { 30 | $config = new ExporterConfig(); 31 | $exporter = new Exporter($config); 32 | 33 | $this->assertFileNotExists('vfs://output/data.csv'); 34 | $exporter->export('vfs://output/data.csv', array( 35 | array('ID', 'name', 'email'), 36 | array('1', 'alice', 'alice@example.com'), 37 | array('2', 'bob', 'bob@example.com'), 38 | )); 39 | 40 | $this->assertFileExists('vfs://output/data.csv'); 41 | $expectedContents = "ID,name,email\r\n"; 42 | $expectedContents .= "1,alice,alice@example.com\r\n"; 43 | $expectedContents .= "2,bob,bob@example.com\r\n"; 44 | $this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv')); 45 | } 46 | 47 | public function test_export_with_carriage_return() 48 | { 49 | $config = new ExporterConfig(); 50 | $config->setNewline("\r"); 51 | $exporter = new Exporter($config); 52 | $exporter->unstrict(); 53 | 54 | $this->assertFileNotExists('vfs://output/data.csv'); 55 | $exporter->export('vfs://output/data.csv', array( 56 | array('aaa', 'bbb', 'ccc', 'dddd'), 57 | array('123', '456', '789'), 58 | array('"aaa"', '"bbb"', '', ''), 59 | )); 60 | 61 | $this->assertFileExists('vfs://output/data.csv'); 62 | $expectedContents = "aaa,bbb,ccc,dddd\r"; 63 | $expectedContents .= "123,456,789\r"; 64 | $expectedContents .= '"""aaa""","""bbb""",,'."\r"; 65 | $this->assertSame($expectedContents, file_get_contents('vfs://output/data.csv')); 66 | } 67 | 68 | public function testUnstrict() 69 | { 70 | $config = new ExporterConfig(); 71 | $exporter = new Exporter($config); 72 | $this->assertAttributeSame(true, 'strict', $exporter); 73 | $exporter->unstrict(); 74 | $this->assertAttributeSame(false, 'strict', $exporter); 75 | } 76 | 77 | /** 78 | * @expectedException \Goodby\CSV\Export\Standard\Exception\StrictViolationException 79 | */ 80 | public function testStrict() 81 | { 82 | $config = new ExporterConfig(); 83 | $exporter = new Exporter($config); 84 | 85 | $exporter->export('vfs://output/data.csv', array( 86 | array('a', 'b', 'c'), 87 | array('a', 'b', 'c'), 88 | array('a', 'b'), 89 | )); 90 | } 91 | 92 | /** 93 | * @requires PHP 5.4 94 | */ 95 | public function test_throwing_IOException_when_failed_to_write_file() 96 | { 97 | $noWritableCsv = 'vfs://output/no-writable.csv'; 98 | touch($noWritableCsv); 99 | chmod($noWritableCsv, 0444); 100 | 101 | $this->assertFalse(is_writable($noWritableCsv)); 102 | 103 | $config = new ExporterConfig(); 104 | $exporter = new Exporter($config); 105 | 106 | $e = null; 107 | 108 | try { 109 | $exporter->export($noWritableCsv, array( 110 | array('a', 'b', 'c'), 111 | )); 112 | } catch ( IOException $e ) { 113 | 114 | } 115 | 116 | $this->assertTrue($e instanceof IOException); 117 | $this->assertContains('failed to open', $e->getMessage()); 118 | } 119 | 120 | public function test_encoding() 121 | { 122 | $csv = 'vfs://output/euc.csv'; 123 | $this->assertFileNotExists($csv); 124 | 125 | $config = new ExporterConfig(); 126 | $config->setToCharset('EUC-JP'); 127 | $config->setNewline("\n"); 128 | $exporter = new Exporter($config); 129 | 130 | $exporter->export($csv, array( 131 | array('あ', 'い', 'う', 'え', 'お'), 132 | )); 133 | 134 | $this->assertFileEquals(__DIR__.'/csv_files/euc-jp.csv', $csv); 135 | } 136 | 137 | public function test_without_encoding() 138 | { 139 | $csv = 'vfs://output/utf-8.csv'; 140 | $this->assertFileNotExists($csv); 141 | 142 | $config = new ExporterConfig(); 143 | $config->setNewline("\n"); 144 | $exporter = new Exporter($config); 145 | 146 | $exporter->export($csv, array( 147 | array('✔', '✔', '✔'), 148 | array('★', '★', '★'), 149 | )); 150 | 151 | $this->assertFileEquals(__DIR__.'/csv_files/utf-8.csv', $csv); 152 | } 153 | 154 | public function test_unseekable_wrapper_and_custom_newline_code() 155 | { 156 | $config = new ExporterConfig(); 157 | $config->setNewline("\r\n"); 158 | $exporter = new Exporter($config); 159 | 160 | ob_start(); 161 | $exporter->export('php://output', array( 162 | array('a', 'b', 'c'), 163 | array('1', '2', '3'), 164 | )); 165 | $output = ob_get_clean(); 166 | 167 | $expectedCount = "a,b,c\r\n1,2,3\r\n"; 168 | $this->assertSame($expectedCount, $output); 169 | } 170 | 171 | public function test_multiple_line_columns() 172 | { 173 | $csv = 'vfs://output/multiple-lines.csv'; 174 | $this->assertFileNotExists($csv); 175 | 176 | $config = new ExporterConfig(); 177 | $config->setNewline("\r\n"); 178 | $exporter = new Exporter($config); 179 | 180 | $exporter->export($csv, array( 181 | array("line1\r\nline2\r\nline3", "single-line"), 182 | array("line1\r\nline2\r\nline3", "single-line"), 183 | array("line1\r\nline2\r\nline3", "single-line"), 184 | )); 185 | 186 | $this->assertFileEquals(__DIR__.'/csv_files/multiple-lines.csv', $csv); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Goodby, CSV 2 | 3 | [![Build Status](https://secure.travis-ci.org/goodby/csv.png?branch=master)](https://travis-ci.org/goodby/csv) 4 | 5 | ## What is "Goodby CSV"? 6 | 7 | Goodby CSV is a highly memory efficient, flexible and extendable open-source CSV import/export library. 8 | 9 | ```php 10 | use Goodby\CSV\Import\Standard\Lexer; 11 | use Goodby\CSV\Import\Standard\Interpreter; 12 | use Goodby\CSV\Import\Standard\LexerConfig; 13 | 14 | $lexer = new Lexer(new LexerConfig()); 15 | $interpreter = new Interpreter(); 16 | $interpreter->addObserver(function(array $row) { 17 | // do something here. 18 | // for example, insert $row to database. 19 | }); 20 | $lexer->parse('data.csv', $interpreter); 21 | ``` 22 | 23 | 24 | ### Features 25 | 26 | #### 1. Memory Management Free 27 | 28 | This library was designed for low memory usage. It will not accumulate all the rows in the memory. The importer reads a CSV file and executes a callback function line by line. 29 | 30 | #### 2. Multibyte support 31 | 32 | This library supports mulitbyte input/output: for example, SJIS-win, EUC-JP and UTF-8. 33 | 34 | #### 3. Ready to Use for Enterprise Applications 35 | 36 | Goodby CSV is fully unit-tested. The library is stable and ready to be used in large projects like enterprise applications. 37 | 38 | ## Requirements 39 | 40 | * PHP 5.3.2 or later 41 | * mbstring 42 | 43 | ## Installation 44 | 45 | Install composer in your project: 46 | 47 | ```bash 48 | curl -s http://getcomposer.org/installer | php 49 | ``` 50 | 51 | Create a `composer.json` file in your project root: 52 | 53 | ```json 54 | { 55 | "require": { 56 | "goodby/csv": "*" 57 | } 58 | } 59 | ``` 60 | 61 | Install via composer: 62 | 63 | ```bash 64 | php composer.phar install 65 | ``` 66 | 67 | ## Documentation 68 | 69 | ### Configuration 70 | 71 | Import configuration: 72 | 73 | ```php 74 | use Goodby\CSV\Import\Standard\LexerConfig; 75 | 76 | $config = new LexerConfig(); 77 | $config 78 | ->setDelimiter("\t") // Customize delimiter. Default value is comma(,) 79 | ->setEnclosure("'") // Customize enclosure. Default value is double quotation(") 80 | ->setEscape("\\") // Customize escape character. Default value is backslash(\) 81 | ->setToCharset('UTF-8') // Customize target encoding. Default value is null, no converting. 82 | ->setFromCharset('SJIS-win') // Customize CSV file encoding. Default value is null. 83 | ; 84 | ``` 85 | 86 | Export configuration: 87 | 88 | ```php 89 | use Goodby\CSV\Export\Standard\ExporterConfig; 90 | 91 | $config = new ExporterConfig(); 92 | $config 93 | ->setDelimiter("\t") // Customize delimiter. Default value is comma(,) 94 | ->setEnclosure("'") // Customize enclosure. Default value is double quotation(") 95 | ->setEscape("\\") // Customize escape character. Default value is backslash(\) 96 | ->setToCharset('SJIS-win') // Customize file encoding. Default value is null, no converting. 97 | ->setFromCharset('UTF-8') // Customize source encoding. Default value is null. 98 | ->setFileMode(CsvFileObject::FILE_MODE_WRITE) // Customize file mode and choose either write or append. Default value is write ('w'). See fopen() php docs 99 | ; 100 | ``` 101 | 102 | ### Unstrict Row Consistency Mode 103 | 104 | By default, Goodby CSV throws `StrictViolationException` when it finds a row with a different column count to other columns. In the case you want to import such a CSV, you can call `Interpreter::unstrict()` to disable row consistency check at import. 105 | 106 | rough.csv: 107 | 108 | ```csv 109 | foo,bar,baz 110 | foo,bar 111 | foo 112 | foo,bar,baz 113 | ``` 114 | 115 | ```php 116 | use Goodby\CSV\Import\Standard\Interpreter; 117 | use Goodby\CSV\Import\Standard\Lexer; 118 | use Goodby\CSV\Import\Standard\LexerConfig; 119 | 120 | $interpreter = new Interpreter(); 121 | $interpreter->unstrict(); // Ignore row column count consistency 122 | 123 | $lexer = new Lexer(new LexerConfig()); 124 | $lexer->parse('rough.csv', $interpreter); 125 | ``` 126 | 127 | ## Examples 128 | 129 | ### Import to Database via PDO 130 | 131 | user.csv: 132 | 133 | ```csv 134 | 1,alice,alice@example.com 135 | 2,bob,bob@example.com 136 | 3,carol,carol@eample.com 137 | ``` 138 | 139 | ```php 140 | use Goodby\CSV\Import\Standard\Lexer; 141 | use Goodby\CSV\Import\Standard\Interpreter; 142 | use Goodby\CSV\Import\Standard\LexerConfig; 143 | 144 | $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root'); 145 | $pdo->query('CREATE TABLE IF NOT EXISTS user (id INT, `name` VARCHAR(255), email VARCHAR(255))'); 146 | 147 | $config = new LexerConfig(); 148 | $lexer = new Lexer($config); 149 | 150 | $interpreter = new Interpreter(); 151 | 152 | $interpreter->addObserver(function(array $columns) use ($pdo) { 153 | $stmt = $pdo->prepare('INSERT INTO user (id, name, email) VALUES (?, ?, ?)'); 154 | $stmt->execute($columns); 155 | }); 156 | 157 | $lexer->parse('user.csv', $interpreter); 158 | ``` 159 | 160 | ### Import from TSV (tab separated values) to array 161 | 162 | temperature.tsv: 163 | 164 | ```csv 165 | 9 Tokyo 166 | 27 Singapore 167 | -5 Seoul 168 | 7 Shanghai 169 | ``` 170 | 171 | ```php 172 | use Goodby\CSV\Import\Standard\Lexer; 173 | use Goodby\CSV\Import\Standard\Interpreter; 174 | use Goodby\CSV\Import\Standard\LexerConfig; 175 | 176 | $temperature = array(); 177 | 178 | $config = new LexerConfig(); 179 | $config->setDelimiter("\t"); 180 | $lexer = new Lexer($config); 181 | 182 | $interpreter = new Interpreter(); 183 | $interpreter->addObserver(function(array $row) use (&$temperature) { 184 | $temperature[] = array( 185 | 'temperature' => $row[0], 186 | 'city' => $row[1], 187 | ); 188 | }); 189 | 190 | $lexer->parse('temperature.tsv', $interpreter); 191 | 192 | print_r($temperature); 193 | ``` 194 | 195 | ### Export from array 196 | 197 | ```php 198 | use Goodby\CSV\Export\Standard\Exporter; 199 | use Goodby\CSV\Export\Standard\ExporterConfig; 200 | 201 | $config = new ExporterConfig(); 202 | $exporter = new Exporter($config); 203 | 204 | $exporter->export('php://output', array( 205 | array('1', 'alice', 'alice@example.com'), 206 | array('2', 'bob', 'bob@example.com'), 207 | array('3', 'carol', 'carol@example.com'), 208 | )); 209 | ``` 210 | 211 | 212 | ### Export from database via PDO 213 | 214 | ```php 215 | use Goodby\CSV\Export\Standard\Exporter; 216 | use Goodby\CSV\Export\Standard\ExporterConfig; 217 | use Goodby\CSV\Export\Standard\CsvFileObject; 218 | use Goodby\CSV\Export\Standard\Collection\PdoCollection; 219 | 220 | $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root'); 221 | 222 | $pdo->query('CREATE TABLE IF NOT EXISTS user (id INT, `name` VARCHAR(255), email VARCHAR(255))'); 223 | $pdo->query("INSERT INTO user VALUES(1, 'alice', 'alice@example.com')"); 224 | $pdo->query("INSERT INTO user VALUES(2, 'bob', 'bob@example.com')"); 225 | $pdo->query("INSERT INTO user VALUES(3, 'carol', 'carol@example.com')"); 226 | 227 | $config = new ExporterConfig(); 228 | $exporter = new Exporter($config); 229 | 230 | $stmt = $pdo->prepare("SELECT * FROM user"); 231 | $stmt->execute(); 232 | 233 | $exporter->export('php://output', new PdoCollection($stmt)); 234 | ``` 235 | 236 | ### Export with CallbackCollection 237 | ```php 238 | use Goodby\CSV\Export\Standard\Exporter; 239 | use Goodby\CSV\Export\Standard\ExporterConfig; 240 | 241 | use Goodby\CSV\Export\Standard\Collection\CallbackCollection; 242 | 243 | $data = array(); 244 | $data[] = array('user', 'name1'); 245 | $data[] = array('user', 'name2'); 246 | $data[] = array('user', 'name3'); 247 | 248 | $collection = new CallbackCollection($data, function($row) { 249 | // apply custom format to the row 250 | $row[1] = $row[1] . '!'; 251 | 252 | return $row; 253 | }); 254 | 255 | $config = new ExporterConfig(); 256 | $exporter = new Exporter($config); 257 | 258 | $exporter->export('php://stdout', $collection); 259 | ``` 260 | 261 | ### Export in Symfony2 action 262 | 263 | ```php 264 | namespace AcmeBundle\ExampleBundle\Controller; 265 | 266 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 267 | use Symfony\Component\HttpFoundation\StreamedResponse; 268 | 269 | class DefaultController extends Controller 270 | { 271 | public function csvExportAction() 272 | { 273 | $conn = $this->get('database_connection'); 274 | 275 | $stmt = $conn->prepare('SELECT * FROM somewhere'); 276 | $stmt->execute(); 277 | 278 | $response = new StreamedResponse(); 279 | $response->setStatusCode(200); 280 | $response->headers->set('Content-Type', 'text/csv'); 281 | $response->setCallback(function() use($stmt) { 282 | $config = new ExporterConfig(); 283 | $exporter = new Exporter($config); 284 | 285 | $exporter->export('php://output', new PdoCollection($stmt->getIterator())); 286 | }); 287 | $response->send(); 288 | 289 | return $response; 290 | } 291 | } 292 | ``` 293 | 294 | ## License 295 | 296 | Csv is open-sourced software licensed under the MIT License - see the LICENSE file for details 297 | 298 | 299 | ## Contributing 300 | 301 | We works under test driven development. 302 | 303 | Checkout master source code from github: 304 | 305 | ```bash 306 | hub clone goodby/csv 307 | ``` 308 | 309 | Install components via composer: 310 | 311 | ``` 312 | # If you don't have composer.phar 313 | ./scripts/bundle-devtools.sh . 314 | 315 | # If you have composer.phar 316 | composer.phar install --dev 317 | ``` 318 | 319 | Run phpunit: 320 | 321 | ```bash 322 | ./vendor/bin/phpunit 323 | ``` 324 | 325 | ## Acknowledgement 326 | 327 | Credits are found within composer.json file. 328 | --------------------------------------------------------------------------------