├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── example ├── benchmarkReader ├── benchmarkWriter ├── cli ├── file │ └── .empty ├── reader └── writer ├── phpunit.xml.dist ├── source ├── AbstractBase.php ├── AbstractFactory.php ├── BaseInterface.php ├── FactoryInterface.php ├── Filter │ ├── AbstractFilter.php │ └── PermeableFilter.php ├── InvalidArgumentException.php ├── Reader │ ├── EasyCsvReaderAdapter.php │ ├── FilteredReader.php │ ├── FilteredReaderFactory.php │ ├── Reader.php │ ├── ReaderFactory.php │ └── ReaderInterface.php ├── RuntimeException.php └── Writer │ ├── EasyCsvWriterAdapter.php │ ├── FilteredWriter.php │ ├── FilteredWriterFactory.php │ ├── FilteredWriterForPhp3Dot3.php │ ├── Writer.php │ ├── WriterFactory.php │ ├── WriterForPhp5Dot3.php │ └── WriterInterface.php └── test ├── AbstractTestCase.php ├── EasyCsvReaderAdapterTest.php ├── EasyCsvWriterAdapterTest.php ├── FilteredReaderTest.php ├── FilteredWriterTest.php ├── ReaderTest.php ├── WriterTest.php ├── bootstrap.php └── data └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | composer.lock 3 | example/file/*.csv 4 | vendor 5 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | paths: 3 | - source/* 4 | excluded_paths: 5 | - vendor/* 6 | 7 | checks: 8 | php: 9 | code_rating: true 10 | duplication: true 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.6 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | - nightly 8 | matrix: 9 | allow_failures: 10 | - php: nightly 11 | before_script: 12 | - composer self-update 13 | - composer install 14 | - phpenv rehash 15 | script: 16 | - vendor/bin/phpunit -v --colors --coverage-text 17 | notifications: 18 | email: 19 | - artodeto@bazzline.net 20 | sudo: false 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Open] 9 | 10 | * add example for filter usage 11 | * add documentation for filter usage 12 | * extend unit tests 13 | * implement \_\_clone(); 14 | * implement usage of filter in writer::copy 15 | * write documentation 16 | * write adapter to easy up migration from [EasyCsv - 0.0.2](https://github.com/jwage/easy-csv/tree/0.0.2/lib/EasyCSV) to this component 17 | 18 | ### To Add 19 | 20 | ### To Change 21 | 22 | ## [Unreleased] 23 | 24 | ### Added 25 | 26 | ### Changed 27 | 28 | ## [1.7.0](https://github.com/bazzline/php_component_csv/tree/1.6.0) - released at 2018-11-07 29 | 30 | ### Added 31 | 32 | * add the php-nightly test in Travis CI build and let this allow failure. 33 | 34 | ### Changed 35 | 36 | * fixed broken composer.json 37 | * let the package require PHP >=5.6 version. 38 | * remove the additional space. 39 | * we don't guarantee that the php-nightly test will always be successful. 40 | * set the different PHPUnit version to support multiple PHP versions. 41 | * using the class-based PHPUnit namespace to be compatible with the latest stable PHPUnit version. 42 | 43 | ## [1.6.0](https://github.com/bazzline/php_component_csv/tree/1.6.0) - released at 2017-05-28 44 | 45 | ### Changed 46 | 47 | * dropped support for php version below 5.6 48 | * moved release history into changelog 49 | * replaced deprecated array syntax "array()" with "[]" 50 | 51 | ## [1.5.14](https://github.com/bazzline/php_component_csv/tree/1.5.14) - released at 2016-05-30 52 | 53 | ### Changed 54 | 55 | * relaxed dependency for mockery 56 | 57 | ## [1.5.13](https://github.com/bazzline/php_component_csv/tree/1.5.13) - released at 2016-03-16 58 | 59 | ### Changed 60 | 61 | * updated dependency 62 | 63 | ## [1.5.12](https://github.com/bazzline/php_component_csv/tree/1.5.12) - released at 2016-02-22 64 | 65 | ### Changed 66 | 67 | * moved to psr-4 autoloading 68 | * removed build status from scrutinizer section 69 | * updated depencenies 70 | 71 | ## [1.5.11](https://github.com/bazzline/php_component_csv/tree/1.5.11) - released at 2016-01-20 72 | 73 | ### Changed 74 | 75 | * updated depencenies 76 | 77 | ## [1.5.10](https://github.com/bazzline/php_component_csv/tree/1.5.10) - released at 2016-01-12 78 | 79 | ### Changed 80 | 81 | * fixed dependency handling for phpunit 4.8.* 82 | 83 | ## [1.5.9](https://github.com/bazzline/php_component_csv/tree/1.5.9) - released at 2015-12-11 84 | 85 | ### Changed 86 | 87 | * updated dependencies 88 | 89 | ## [1.5.8](https://github.com/bazzline/php_component_csv/tree/1.5.8) - released at 2015-11-08 90 | 91 | ### Changed 92 | 93 | * updated dependencies 94 | 95 | ## [1.5.7](https://github.com/bazzline/php_component_csv/tree/1.5.7) - released at 2015-10-07 96 | 97 | ### Changed 98 | 99 | * updated dependencies 100 | 101 | ## [1.5.6](https://github.com/bazzline/php_component_csv/tree/1.5.6) - released at 2015-09-10 102 | 103 | ### Changed 104 | 105 | * relaxed dependencies 106 | 107 | ## [1.5.5](https://github.com/bazzline/php_component_csv/tree/1.5.5) - released at 2015-09-10 108 | 109 | ### Changed 110 | 111 | * relaxed dependencies 112 | 113 | ## [1.5.4](https://github.com/bazzline/php_component_csv/tree/1.5.4) - released at 2015-09-09 114 | 115 | ### Added 116 | 117 | * added `BaseInterface`, `ReaderInterface` and `WriterInterface` 118 | 119 | ## [1.5.3](https://github.com/bazzline/php_component_csv/tree/1.5.3) - released at 2015-08-26 120 | 121 | ### Changed 122 | 123 | * updated dependencies 124 | 125 | ## [1.5.2](https://github.com/bazzline/php_component_csv/tree/1.5.2) - released at 2015-07-06 126 | 127 | ### Changed 128 | 129 | * refactored [cli](https://github.com/bazzline/php_component_csv/blob/master/example/cli) example by using [php_component_cli_readline](https://github.com/bazzline/php_component_cli_readline) 130 | 131 | ## [1.5.1](https://github.com/bazzline/php_component_csv/tree/1.5.1) - released at 2015-07-04 132 | 133 | ### Changed 134 | 135 | * updated dependency 136 | 137 | ## [1.5.0](https://github.com/bazzline/php_component_csv/tree/1.5.0) - released at 2015-07-02 138 | 139 | ### Added 140 | 141 | * added dependency to [generic agreement](https://github.com/bazzline/php_component_generic_agreement) 142 | 143 | ### Changed 144 | 145 | * replaced own [FilterInterface](https://github.com/bazzline/php_component_csv/blob/1.4.0/source/Net/Bazzline/Component/Csv/Filter/FilterInterface.php) with external [FilterInterface](https://github.com/bazzline/php_component_generic_agreement/blob/master/source/Net/Bazzline/Component/GenericAgreement/Data/FilterableInterface.php) 146 | 147 | ## [1.4.0](https://github.com/bazzline/php_component_csv/tree/1.4.0) - released at 2015-07-02 148 | 149 | ### Added 150 | 151 | * started [cli](https://github.com/bazzline/php_component_csv/blob/master/example/cli) example to easy up usage 152 | * added "rewind" call when using reader::readAll() and reader::readMany() 153 | 154 | ## [1.3.0](https://github.com/bazzline/php_component_csv/tree/1.3.0) - released at 2015-06-26 155 | 156 | ### Added 157 | 158 | 159 | * added headline output support as keys for Reader::readMany() 160 | * added headline output support as keys for Reader::readOne() 161 | * can be disabled by Reader::disableAddHeadlineToOutput() 162 | * can be enabled by Reader::enableAddHeadlineToOutput() 163 | * is enabled by default 164 | 165 | ### Changed 166 | 167 | * fixed broken unit test for php 5.3 168 | * moved complex array combine into [own project](https://github.com/bazzline/php_component_toolbox/blob/master/source/Net/Bazzline/Component/Toolbox/HashMap/Combine.php) 169 | * removed duplicated code in Reader 170 | 171 | ## [1.2.0](https://github.com/bazzline/php_component_csv/tree/1.2.0) - released at 2015-06-25 172 | 173 | ### Added 174 | 175 | * added examples ([benchmarkReader](https://github.com/bazzline/php_component_csv/blob/master/example/benchmarkReader), [benchmarkWriter](https://github.com/bazzline/php_component_csv/blob/master/example/benchmarkWriter), [reader](https://github.com/bazzline/php_component_csv/blob/master/example/reader) and [writer](https://github.com/bazzline/php_component_csv/blob/master/example/writer)) 176 | * implemented filter for reader and writer by creating the [FilterInterface](https://github.com/bazzline/php_component_csv/blob/1.2.0/source/Net/Bazzline/Component/Csv/Filter/FilterInterface.php) 177 | 178 | ## [1.1.0](https://github.com/bazzline/php_component_csv/tree/1.1.0) - released at 2015-06-10 179 | 180 | ### Added 181 | 182 | * added link to api 183 | * added minimum php version requirement 184 | * implemented "move($path)" method into [Writer](https://github.com/bazzline/php_component_csv/blob/master/source/Net/Bazzline/Component/Csv/Writer/Writer.php) 185 | 186 | ### Changed 187 | 188 | * removed "TODO" 189 | * updated dependencies 190 | 191 | ## [1.0.0](https://github.com/bazzline/php_component_csv/tree/1.0.0) - released at 2015-06-07 192 | 193 | ### Added 194 | 195 | * initial release 196 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/spatie/laravel-backup). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ vendor/bin/phpunit -v --colors --coverage-text 29 | ``` 30 | 31 | 32 | **Happy coding**! 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSV Handling Component for PHP 2 | 3 | This project aims to deliver an easy to use and free as in freedom php compontent for dealing with csv files (read and write). 4 | 5 | This component is heavily influenced by [jwage/easy-csv](https://github.com/jwage/easy-csv). 6 | It was mainly created because of missing compatibility with php 5.6 and no official packagist support from [jwage/easy-csv](https://github.com/jwage/easy-csv). 7 | 8 | The build status of the current master branch is tracked by Travis CI: 9 | [![Build Status](https://travis-ci.org/bazzline/php_component_csv.png?branch=master)](http://travis-ci.org/bazzline/php_component_csv) 10 | [![Latest stable](https://img.shields.io/packagist/v/net_bazzline/php_component_csv.svg)](https://packagist.org/packages/net_bazzline/php_component_csv) 11 | 12 | The scrutinizer status are: 13 | [![code quality](https://scrutinizer-ci.com/g/bazzline/php_component_csv/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/bazzline/php_component_csv/) 14 | 15 | The versioneye status is: 16 | [![Dependency Status](https://www.versioneye.com/user/projects/557492b1316137000d0000d0/badge.svg?style=flat)](https://www.versioneye.com/user/projects/557492b1316137000d0000d0) 17 | 18 | Take a look on [openhub.net](https://www.openhub.net/p/php_component_csv). 19 | 20 | The current change log can be found [here](https://github.com/bazzline/php_component_csv/blob/master/CHANGELOG.md). 21 | 22 | # Benefits 23 | 24 | * low and stable memory usage (give it a try by using [benchmarkReader](https://github.com/bazzline/php_component_csv/blob/master/example/benchmarkReader) and [benchmarkWriter](https://github.com/bazzline/php_component_csv/blob/master/example/benchmarkWriter)) 25 | * works with PHP 5.6 and above 26 | * \_\_invoke() implemented to use it as function 27 | * unified reader and writer 28 | * adapter to easy up migration from [EasyCsv - 0.0.1](https://github.com/jwage/easy-csv/tree/0.0.1/lib/EasyCSV) to this component 29 | * [writer](https://github.com/jwage/easy-csv/blob/master/lib/EasyCSV/Writer.php) 30 | * [reader](https://github.com/jwage/easy-csv/blob/master/lib/EasyCSV/Reader.php) 31 | * usage of [filters](https://github.com/bazzline/php_component_csv/blob/master/source/Net/Bazzline/Component/Csv/Filter) - control what comes in and what comes out 32 | * reader 33 | * implemented iterator 34 | * readOne(); 35 | * readMany(); 36 | * readAll(); 37 | * writer 38 | * copy(); 39 | * delete(); 40 | * move(); 41 | * truncate(); 42 | * writeOne(); 43 | * writeMany(); 44 | * writeAll(); //truncates file and writes content 45 | 46 | # Install 47 | 48 | ## By Hand 49 | 50 | ``` 51 | mkdir -p vendor/net_bazzline/php_component_csv 52 | cd vendor/net_bazzline/php_component_csv 53 | git clone https://github.com/bazzline/php_component_csv . 54 | ``` 55 | 56 | ## With [Packagist](https://packagist.org/packages/net_bazzline/php_component_csv) 57 | 58 | ``` 59 | composer require net_bazzline/php_component_csv:dev-master 60 | ``` 61 | 62 | # Usage 63 | 64 | ## [Reader](http://www.bazzline.net/55371e9f93dbdec83dc82730a5a73db5fc36272e/class-Net.Bazzline.Component.Csv.Reader.Reader.html) 65 | 66 | ### Read Content 67 | 68 | ```php 69 | $reader = new Reader('my/file.csv'); 70 | //I am using json_encode() since there is no official and best way how to 71 | // output arrays on the command line. 72 | 73 | //read one line 74 | echo json_encode($reader->readOne()) . PHP_EOL; 75 | 76 | //read 10 lines 77 | foreach ($reader->readMany(10) as $line) { 78 | echo json_encode($line) . PHP_EOL; 79 | } 80 | 81 | //read all lines 82 | foreach ($reader->readAll() as $line) { 83 | echo json_encode($line) . PHP_EOL; 84 | } 85 | ``` 86 | 87 | #### By Iteration 88 | 89 | ```php 90 | $reader = new Reader('my/file.csv'); 91 | //I am using json_encode() since there is no official and best way how to 92 | // output arrays on the command line. 93 | 94 | if ($reader->hasHeadline()) { 95 | echo 'headlines: ' . json_encode($reader->readHeadline()); 96 | } 97 | 98 | foreach ($reader as $line) { 99 | echo json_encode($line) . PHP_EOL; 100 | } 101 | ``` 102 | 103 | #### By Using As A Function 104 | 105 | ```php 106 | $reader = new Reader('my/file.csv'); 107 | //I am using json_encode() since there is no official and best way how to 108 | // output arrays on the command line. 109 | 110 | while ($line = $reader()) { 111 | echo json_encode($line) . PHP_EOL; 112 | } 113 | ``` 114 | 115 | ## [Writer](http://www.bazzline.net/55371e9f93dbdec83dc82730a5a73db5fc36272e/class-Net.Bazzline.Component.Csv.Writer.Writer.html) 116 | 117 | ### Write Content 118 | 119 | #### By Iteration 120 | 121 | ```php 122 | //$headlines contains a php array 123 | //$lines contains a php array of arrays 124 | $writer = new Writer('my/file.csv'); 125 | 126 | $writer->writeHeadline($headlines); 127 | 128 | foreach ($lines as $line) { 129 | $writer->writeOne($line); 130 | } 131 | ``` 132 | 133 | #### At Once 134 | 135 | ```php 136 | //$headlines contains a php array 137 | //$lines contains a php array of arrays 138 | $writer = new Writer('my/file.csv'); 139 | 140 | $writer->writeHeadline($headlines); 141 | $writer->writeMany($lines); 142 | ``` 143 | 144 | #### By Using As A Function 145 | 146 | ```php 147 | //$line contains a php array 148 | //$lines contains a php array of arrays 149 | $writer = new Writer('my/file.csv'); 150 | 151 | $writer($line); 152 | 153 | foreach ($lines as $line) { 154 | $writer($line); 155 | } 156 | ``` 157 | 158 | ### Truncate 159 | 160 | ```php 161 | $writer = new Writer('my/file.csv'); 162 | 163 | $writer->truncate(); 164 | ``` 165 | 166 | ### Copy 167 | 168 | ```php 169 | $writer = new Writer('my/file.csv'); 170 | 171 | $writer->copy('my/my_first_copy.csv'); //writer will still write into "file.csv" 172 | 173 | $writer->copy('my/my_second_copy.csv', true); //writer will write in "my_second_copy.csv" 174 | ``` 175 | 176 | ### Move 177 | 178 | ```php 179 | $writer = new Writer('my/file.csv'); 180 | 181 | $writer->move('my/new_name.csv'); //writer will write in "new_name.csv" 182 | ``` 183 | 184 | # API 185 | 186 | [API](http://www.bazzline.net/55371e9f93dbdec83dc82730a5a73db5fc36272e/index.html) is available at [bazzline.net](http://www.bazzline.net). 187 | 188 | # Other Great Components 189 | 190 | * [goodby/csv](https://github.com/goodby/csv) 191 | * [thephpleague/csv](https://github.com/thephpleague/csv) 192 | * [keboola/php-csv](https://github.com/keboola/php-csv) 193 | * [ajgarlag/AiglCsv](https://github.com/ajgarlag/AjglCsv) 194 | * [jwage/easy-csv](https://github.com/jwage/easy-csv) 195 | * [swt83/php-csv](https://github.com/swt83/php-csv) 196 | 197 | # Hall of Fame - The list of contributors 198 | 199 | * [peter279k](https://github.com/peter279k) - [homepage](https://peterli.website) 200 | * [stevleibelt](https://github.com/stevleibelt) - [homepage](https://stev.leibelt.de) 201 | 202 | # Contributing 203 | 204 | Please see [CONTRIBUTING](https://github.com/bazzline/php_component_csv/blob/master/CONTRIBUTING.md) for details. 205 | 206 | # Final Words 207 | 208 | Star it if you like it :-). Add issues if you need it. Pull patches if you enjoy it. Write a blog entry if you use it. [Donate something](https://gratipay.com/~stevleibelt) if you love it :-]. 209 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "net_bazzline/php_component_csv", 3 | "description": "free as in freedom php component to easy up usage (reading and writing) of csv files for php 5.6 and above", 4 | "keywords": [ 5 | "bazzline", 6 | "psr", 7 | "psr-4", 8 | "php", 9 | "php56", 10 | "php7", 11 | "component", 12 | "filesystem", 13 | "easy", 14 | "csv", 15 | "easycsv", 16 | "adapter", 17 | "read", 18 | "write", 19 | "lgpl", 20 | "filter", 21 | "import", 22 | "export", 23 | "headline", 24 | "invoke", 25 | "tranversable", 26 | "iterator", 27 | "reusable", 28 | "free as in freedom", 29 | "low", 30 | "memory", 31 | "usage" 32 | ], 33 | "type": "library", 34 | "require": { 35 | "net_bazzline/php_component_cli_readline": "^1.2", 36 | "net_bazzline/php_component_generic_agreement": "^1.0", 37 | "net_bazzline/php_component_toolbox": "^1.3", 38 | "php": ">=5.6" 39 | }, 40 | "require-dev": { 41 | "mikey179/vfsstream": "^1.6", 42 | "mockery/mockery": "^0.9", 43 | "phpunit/phpunit": "^5.7 || ^6.5" 44 | }, 45 | "license": "LGPL-3.0", 46 | "authors": [ 47 | { 48 | "name": "Stev Leibelt", 49 | "email": "artodeto@bazzline.net", 50 | "homepage": "https://artodeto.bazzline.net", 51 | "role": "Developer" 52 | } 53 | ], 54 | "autoload": { 55 | "psr-4": { 56 | "Net\\Bazzline\\Component\\Csv\\": "source/" 57 | } 58 | }, 59 | "autoload-dev": { 60 | "psr-4": { 61 | "Test\\Net\\Bazzline\\Component\\Csv\\": "test/" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /example/benchmarkReader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | * @since 2015-06-24 6 | */ 7 | 8 | require_once __DIR__ . '/../vendor/autoload.php'; 9 | 10 | $factory = new \Net\Bazzline\Component\Csv\Reader\ReaderFactory(); 11 | $reader = $factory->create(); 12 | 13 | try { 14 | $path = ($argc > 1) ? $argv[1] : __DIR__ . '/file/benchmark.csv'; 15 | 16 | if (!file_exists($path)) { 17 | throw new Exception('invalid file path provided: "' . $path . '"'); 18 | } 19 | 20 | $reader->setPath($path); 21 | 22 | $lineIterator = 0; 23 | $memoryUsageBeforeInMegabytes = (memory_get_usage(true) / 1048576); 24 | $memoryPeakUsageBeforeInMegabytes = (memory_get_peak_usage(true) / 1048576); 25 | $timeBeforeInSeconds = microtime(true); 26 | 27 | while ($line = $reader()) { 28 | ++$lineIterator; 29 | if (($lineIterator % 100) === 0) { 30 | echo '.'; 31 | } 32 | } 33 | echo PHP_EOL; 34 | 35 | $memoryUsageAfterInMegabytes = (memory_get_usage(true) / 1048576); 36 | $memoryPeakUsageAfterInMegabytes = (memory_get_peak_usage(true) / 1048576); 37 | $timeAfterInMicroSeconds = microtime(true); 38 | 39 | echo 'file path: ' . realpath($path) . PHP_EOL; 40 | echo 'number of lines read: ' . $lineIterator . PHP_EOL; 41 | echo 'runtime: ' . ceil($timeAfterInMicroSeconds - $timeBeforeInSeconds) . ' seconds' . PHP_EOL; 42 | echo 'memory usage' . PHP_EOL; 43 | echo ' before writing: ' . $memoryUsageBeforeInMegabytes . ' MB' . PHP_EOL; 44 | echo ' after writing: ' . $memoryUsageAfterInMegabytes . ' MB' . PHP_EOL; 45 | echo 'memory peak usage' . PHP_EOL; 46 | echo ' before writing: ' . $memoryPeakUsageBeforeInMegabytes . ' MB' . PHP_EOL; 47 | echo ' after writing: ' . $memoryPeakUsageAfterInMegabytes . ' MB' . PHP_EOL; 48 | } catch (Exception $exception) { 49 | echo 'usage: ' . basename(__FILE__) . ' []' . PHP_EOL; 50 | echo '----------------' . PHP_EOL; 51 | echo $exception->getMessage() . PHP_EOL; 52 | return 1; 53 | } 54 | -------------------------------------------------------------------------------- /example/benchmarkWriter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | * @since 2015-06-24 6 | */ 7 | 8 | require_once __DIR__ . '/../vendor/autoload.php'; 9 | 10 | $factory = new \Net\Bazzline\Component\Csv\Writer\WriterFactory(); 11 | $writer = $factory->create(); 12 | 13 | try { 14 | $usage = 'usage: ' . basename(__FILE__) . ' []'; 15 | 16 | if ($argc < 3) { 17 | throw new Exception('invalid number of parameters provided'); 18 | } 19 | array_shift($argv); 20 | end($argv); 21 | 22 | $path = (is_file(current($argv))) ? array_pop($argv) : __DIR__ . '/file/benchmark.csv'; 23 | reset($argv); 24 | 25 | $writer->setPath($path); 26 | 27 | $arrayOfLine = []; 28 | $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 '; 29 | $numberOfCharacters = strlen($characters); 30 | $numberOfCharacterIndex = $numberOfCharacters - 1; 31 | $numberOfColumnsPerLine = array_shift($argv); 32 | $numberOfLines = array_shift($argv); 33 | 34 | $memoryUsageBeforeInMegabytes = (memory_get_usage(true) / 1048576); 35 | $memoryPeakUsageBeforeInMegabytes = (memory_get_peak_usage(true) / 1048576); 36 | $timeBeforeInMicroSeconds = microtime(true); 37 | 38 | for ($lineIterator = 0; $lineIterator < $numberOfLines; ++$lineIterator) { 39 | $columns = []; 40 | 41 | for ($columnIterator = 0; $columnIterator < $numberOfColumnsPerLine; ++$columnIterator) { 42 | $column = ''; 43 | $numberOfCharactersPerColumn = mt_rand(1, $numberOfCharacters); 44 | 45 | for ($characterIterator = 0; $characterIterator < $numberOfCharactersPerColumn; ++$characterIterator) { 46 | $index = mt_rand(0, $numberOfCharacterIndex); 47 | $column .= $characters[$index]; 48 | } 49 | 50 | $columns[] = $column; 51 | } 52 | 53 | $line = implode(',', $columns); 54 | 55 | if ($writer($line) === false) { 56 | throw new Exception('could not write line "' . $line . '" to file "' . $path . '"'); 57 | } else { 58 | if (($lineIterator % 100) === 0) { 59 | echo '.'; 60 | } 61 | } 62 | } 63 | echo PHP_EOL; 64 | 65 | $memoryUsageAfterInMegabytes = (memory_get_usage(true) / 1048576); 66 | $memoryPeakUsageAfterInMegabytes = (memory_get_peak_usage(true) / 1048576); 67 | $timeAfterInMicroSeconds = microtime(true); 68 | 69 | echo 'file path: ' . realpath($path) . PHP_EOL; 70 | echo 'number of lines written: ' . $lineIterator . PHP_EOL; 71 | echo 'runtime: ' . ceil($timeAfterInMicroSeconds - $timeBeforeInMicroSeconds) . ' seconds' . PHP_EOL; 72 | echo 'memory usage' . PHP_EOL; 73 | echo ' before writing: ' . $memoryUsageBeforeInMegabytes . ' MB' . PHP_EOL; 74 | echo ' after writing: ' . $memoryUsageAfterInMegabytes . ' MB' . PHP_EOL; 75 | echo 'memory peak usage' . PHP_EOL; 76 | echo ' before writing: ' . $memoryPeakUsageBeforeInMegabytes . ' MB' . PHP_EOL; 77 | echo ' after writing: ' . $memoryPeakUsageAfterInMegabytes . ' MB' . PHP_EOL; 78 | } catch (Exception $exception) { 79 | echo $usage . PHP_EOL; 80 | echo '----------------' . PHP_EOL; 81 | echo $exception->getMessage() . PHP_EOL; 82 | return 1; 83 | } 84 | -------------------------------------------------------------------------------- /example/cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | * @since 2015-06-30 6 | * @see: 7 | * https://github.com/stevleibelt/examples/blob/master/php/cli/readline.php 8 | * https://github.com/yiisoft/yii2/issues/7974 9 | * https://github.com/ErikDubbelboer/php-repl/blob/master/repl.php 10 | */ 11 | 12 | use Net\Bazzline\Component\Cli\Readline\ManagerFactory; 13 | use Net\Bazzline\Component\Csv\Reader\ReaderFactory; 14 | use Net\Bazzline\Component\Csv\Writer\WriterFactory; 15 | 16 | require_once __DIR__ . '/../vendor/autoload.php'; 17 | 18 | try { 19 | $path = ($argc > 1) ? $argv[1] : __DIR__ . '/file/example.csv'; 20 | 21 | if (!file_exists($path)) { 22 | throw new Exception('invalid file path provided: "' . $path . '"'); 23 | } 24 | 25 | $managerFactory = new ManagerFactory(); 26 | $readerFactory = new ReaderFactory(); 27 | $writerFactory = new WriterFactory(); 28 | 29 | $manager = $managerFactory->create(); 30 | $reader = $readerFactory->create(); 31 | $writer = $writerFactory->create(); 32 | 33 | $reader->setPath($path); 34 | $writer->setPath($path); 35 | 36 | $manager->setConfiguration( 37 | [ 38 | 'exit' => function () { 39 | exit(0); 40 | }, 41 | 'help' => function () { 42 | echo 'usage: ' . PHP_EOL . 43 | ' ' . basename(__FILE__) . ' [path to csv file]' . PHP_EOL; 44 | }, 45 | 'read' => [ 46 | 'all' => function () use ($reader) { 47 | foreach ($reader->readAll() as $line) { 48 | echo implode("\t", $line) . PHP_EOL; 49 | } 50 | }, 51 | 'many' => function($length = null, $start = null) use ($reader) { 52 | $showUsage = (is_null($length) || ((int) $length === 0)); 53 | if ($showUsage) { 54 | echo 'usage: ' . PHP_EOL . 55 | ' many []' . PHP_EOL; 56 | } else { 57 | foreach ($reader->readMany($length, $start) as $line) { 58 | echo implode("\t", $line) . PHP_EOL; 59 | } 60 | } 61 | }, 62 | 'one' => function ($lineNumber = null) use ($reader) { 63 | $showUsage = (is_null($lineNumber)); 64 | if ($showUsage) { 65 | echo 'usage: ' . PHP_EOL . 66 | ' one ' . PHP_EOL; 67 | } else { 68 | $line = $reader->readOne($lineNumber); 69 | 70 | if (is_array($line)) { 71 | echo implode("\t", $line) . PHP_EOL; 72 | } else if (is_scalar($line)) { 73 | echo $line . PHP_EOL; 74 | } 75 | } 76 | } 77 | ], 78 | 'write' => [ 79 | 'all' => function ($lines = null) use ($writer) { 80 | //@todo move into closure to reuse it 81 | $lines = func_get_args(); 82 | $numberOfLines = false; 83 | 84 | if (is_array($lines)) { 85 | $numberOfLines = $writer->writeMany($lines); 86 | } 87 | 88 | if ($numberOfLines === false) { 89 | echo 'no lines where written' . PHP_EOL; 90 | } else { 91 | echo count($lines) . ' lines written' . PHP_EOL; 92 | } 93 | }, 94 | 'many' => function () use ($writer) { 95 | $lines = func_get_args(); 96 | $numberOfLines = false; 97 | 98 | if (is_array($lines)) { 99 | $numberOfLines = $writer->writeMany($lines); 100 | } 101 | 102 | if ($numberOfLines === false) { 103 | echo 'no lines where written' . PHP_EOL; 104 | } else { 105 | echo count($lines) . ' lines written' . PHP_EOL; 106 | } 107 | }, 108 | 'one' => function () use ($writer) { 109 | $arguments = func_get_args(); 110 | $numberOfArguments = count($arguments); 111 | 112 | if ($numberOfArguments === 1) { 113 | $line = $arguments[0]; 114 | $numberOfLines = (is_scalar($line)) ? $writer->writeOne($line) : false; 115 | } else if ($numberOfArguments > 1) { 116 | $line = implode(',', $arguments); 117 | $numberOfLines = (is_scalar($line)) ? $writer->writeOne($line) : false; 118 | } else { 119 | $numberOfLines = false; 120 | } 121 | 122 | if ($numberOfLines === false) { 123 | echo 'no lines where written' . PHP_EOL; 124 | } else { 125 | echo '1 line written' . PHP_EOL; 126 | } 127 | } 128 | ] 129 | ] 130 | ); 131 | $manager->setPrompt('csv cli: '); 132 | $manager->run(); 133 | } catch (Exception $exception) { 134 | echo 'usage: ' . basename(__FILE__) . ' []' . PHP_EOL; 135 | echo '----------------' . PHP_EOL; 136 | echo $exception->getMessage() . PHP_EOL; 137 | return 1; 138 | } 139 | -------------------------------------------------------------------------------- /example/file/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bazzline/php_component_csv/539360c4aed599d52756f8ddb1e3fb206742b3ca/example/file/.empty -------------------------------------------------------------------------------- /example/reader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | * @since 2015-06-24 6 | */ 7 | 8 | require_once __DIR__ . '/../vendor/autoload.php'; 9 | 10 | $factory = new \Net\Bazzline\Component\Csv\Reader\ReaderFactory(); 11 | $reader = $factory->create(); 12 | 13 | try { 14 | $path = ($argc > 1) ? $argv[1] : __DIR__ . '/file/example.csv'; 15 | 16 | if (!file_exists($path)) { 17 | throw new Exception('invalid file path provided: "' . $path . '"'); 18 | } 19 | 20 | $reader->setPath($path); 21 | 22 | while ($line = $reader()) { 23 | echo implode("\t", $line) . PHP_EOL; 24 | } 25 | } catch (Exception $exception) { 26 | echo 'usage: ' . basename(__FILE__) . ' []' . PHP_EOL; 27 | echo '----------------' . PHP_EOL; 28 | echo $exception->getMessage() . PHP_EOL; 29 | return 1; 30 | } 31 | -------------------------------------------------------------------------------- /example/writer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | * @since 2015-06-24 6 | */ 7 | 8 | require_once __DIR__ . '/../vendor/autoload.php'; 9 | 10 | $factory = new \Net\Bazzline\Component\Csv\Writer\WriterFactory(); 11 | $writer = $factory->create(); 12 | 13 | try { 14 | $usage = 'usage: ' . basename(__FILE__) . ' "content,of,line,one" ["content,of,line,two"[...[]]]'; 15 | 16 | if ($argc < 2) { 17 | throw new Exception('you have to provide at least one line of content'); 18 | } 19 | array_shift($argv); 20 | end($argv); 21 | 22 | $path = (is_file(current($argv))) ? array_pop($argv) : __DIR__ . '/file/example.csv'; 23 | reset($argv); 24 | 25 | $writer->setPath($path); 26 | 27 | $writer('asdasds" asdasd'); 28 | 29 | foreach ($argv as $line) { 30 | if ($writer($line) === false) { 31 | throw new Exception('could not write line "' . $line . '" to file "' . $path . '"'); 32 | } 33 | } 34 | } catch (Exception $exception) { 35 | echo $usage . PHP_EOL; 36 | echo '----------------' . PHP_EOL; 37 | echo $exception->getMessage() . PHP_EOL; 38 | return 1; 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | test/ 15 | 16 | 17 | 18 | 19 | source 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /source/AbstractBase.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-06 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv; 8 | 9 | use SplFileObject; 10 | 11 | abstract class AbstractBase implements BaseInterface 12 | { 13 | /** @var string */ 14 | private $delimiter = ','; 15 | 16 | /** @var string */ 17 | private $enclosure = '"'; 18 | 19 | /** @var string */ 20 | private $escapeCharacter = '\\'; 21 | 22 | /** @var false|array */ 23 | private $headline = false; 24 | 25 | /** @var SplFileObject */ 26 | private $handler; 27 | 28 | /** @var string */ 29 | private $path; 30 | 31 | /** 32 | * @return bool 33 | */ 34 | public function hasHeadline() 35 | { 36 | return ($this->headline !== false); 37 | } 38 | 39 | /** 40 | * @param string $delimiter 41 | * @throws InvalidArgumentException 42 | */ 43 | public function setDelimiter($delimiter) 44 | { 45 | $this->assertIsASingleCharacterString($delimiter, 'delimiter'); 46 | $this->delimiter = $delimiter; 47 | $this->updateCsvControl(); 48 | } 49 | 50 | /** 51 | * @param string $enclosure 52 | * @throws InvalidArgumentException 53 | */ 54 | public function setEnclosure($enclosure) 55 | { 56 | $this->assertIsASingleCharacterString($enclosure, 'enclosure'); 57 | $this->enclosure = $enclosure; 58 | $this->updateCsvControl(); 59 | } 60 | 61 | /** 62 | * @param string $escapeCharacter 63 | * @throws InvalidArgumentException 64 | */ 65 | public function setEscapeCharacter($escapeCharacter) 66 | { 67 | $this->assertIsASingleCharacterString($escapeCharacter, 'escapeCharacter'); 68 | $this->escapeCharacter = $escapeCharacter; 69 | $this->updateCsvControl(); 70 | } 71 | 72 | /** 73 | * @param string $path 74 | * @return $this 75 | * @throws InvalidArgumentException 76 | * @todo implement validation 77 | */ 78 | public function setPath($path) 79 | { 80 | $this->path = $path; 81 | $this->handler = $this->open($path); 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * @return string 88 | */ 89 | protected function getDelimiter() 90 | { 91 | return $this->delimiter; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | protected function getEnclosure() 98 | { 99 | return $this->enclosure; 100 | } 101 | 102 | /** 103 | * @return string 104 | */ 105 | protected function getEscapeCharacter() 106 | { 107 | return $this->escapeCharacter; 108 | } 109 | 110 | /** 111 | * @return SplFileObject|resource 112 | */ 113 | protected function getFileHandler() 114 | { 115 | return $this->handler; 116 | } 117 | 118 | /** 119 | * @return string 120 | */ 121 | abstract protected function getFileHandlerOpenMode(); 122 | 123 | /** 124 | * @return array|false 125 | */ 126 | protected function getHeadline() 127 | { 128 | return $this->headline; 129 | } 130 | 131 | /** 132 | * @return string 133 | */ 134 | protected function getPath() 135 | { 136 | return $this->path; 137 | } 138 | 139 | /** 140 | * @return $this 141 | */ 142 | protected function resetHeadline() 143 | { 144 | $this->headline = false; 145 | 146 | return $this; 147 | } 148 | 149 | /** 150 | * @param array $headline 151 | * @return $this 152 | */ 153 | protected function setHeadline(array $headline) 154 | { 155 | $this->headline = $headline; 156 | 157 | return $this; 158 | } 159 | 160 | protected function close() 161 | { 162 | if (!is_null($this->handler)) { 163 | $this->headline = null; 164 | } 165 | } 166 | 167 | /** 168 | * @param string $path 169 | * @return SplFileObject 170 | * @todo inject or inject factory 171 | */ 172 | protected function open($path) 173 | { 174 | $file = new SplFileObject($path, $this->getFileHandlerOpenMode()); 175 | $file->setFlags(SplFileObject::READ_CSV | SplFileObject::DROP_NEW_LINE | SplFileObject::SKIP_EMPTY); 176 | 177 | return $file; 178 | } 179 | 180 | /** 181 | * @param string $variable 182 | * @param string $name 183 | * @throws InvalidArgumentException 184 | */ 185 | private function assertIsASingleCharacterString($variable, $name) 186 | { 187 | if (!is_string($variable)) { 188 | $message = $name . ' must be of type "string"'; 189 | 190 | throw new InvalidArgumentException($message); 191 | } 192 | if (strlen($variable) != 1) { 193 | $message = $name . ' must be a single character'; 194 | 195 | throw new InvalidArgumentException($message); 196 | } 197 | } 198 | 199 | private function updateCsvControl() 200 | { 201 | $file = $this->getFileHandler(); 202 | 203 | if ($file instanceof SplFileObject) { 204 | $file->setCsvControl( 205 | $this->getDelimiter(), 206 | $this->getEnclosure(), 207 | $this->getEscapeCharacter() 208 | ); 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /source/AbstractFactory.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-06 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv; 8 | 9 | /** 10 | * Class AbstractFactory 11 | * @package Net\Bazzline\Component\Csv 12 | */ 13 | abstract class AbstractFactory implements FactoryInterface 14 | { 15 | /** 16 | * @return string 17 | */ 18 | protected function getDelimiter() 19 | { 20 | return ','; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | protected function getEnclosure() 27 | { 28 | return '"'; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | protected function getEscapeCharacter() 35 | { 36 | return "\\"; 37 | } 38 | } -------------------------------------------------------------------------------- /source/BaseInterface.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-09-09 5 | */ 6 | namespace Net\Bazzline\Component\Csv; 7 | 8 | /** 9 | * Interface BaseInterface 10 | * @package Net\Bazzline\Component\Csv 11 | */ 12 | interface BaseInterface 13 | { 14 | /** 15 | * @return bool 16 | */ 17 | public function hasHeadline(); 18 | 19 | /** 20 | * @param string $delimiter 21 | * @throws InvalidArgumentException 22 | */ 23 | public function setDelimiter($delimiter); 24 | 25 | /** 26 | * @param string $enclosure 27 | * @throws InvalidArgumentException 28 | */ 29 | public function setEnclosure($enclosure); 30 | 31 | /** 32 | * @param string $escapeCharacter 33 | * @throws InvalidArgumentException 34 | */ 35 | public function setEscapeCharacter($escapeCharacter); 36 | 37 | /** 38 | * @param string $path 39 | * @return $this 40 | * @throws InvalidArgumentException 41 | */ 42 | public function setPath($path); 43 | } -------------------------------------------------------------------------------- /source/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-06 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv; 8 | 9 | interface FactoryInterface 10 | { 11 | /** 12 | * @return object 13 | */ 14 | public function create(); 15 | } -------------------------------------------------------------------------------- /source/Filter/AbstractFilter.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-07-02 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Filter; 8 | 9 | use Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface; 10 | use Net\Bazzline\Component\GenericAgreement\Exception\ExceptionInterface; 11 | 12 | abstract class AbstractFilter implements FilterableInterface 13 | { 14 | /** 15 | * @param mixed $data 16 | * @return null|mixed 17 | * @throws ExceptionInterface 18 | */ 19 | public function __invoke($data) 20 | { 21 | return $this->filter($data); 22 | } 23 | } -------------------------------------------------------------------------------- /source/Filter/PermeableFilter.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-14 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Filter; 8 | 9 | use Net\Bazzline\Component\GenericAgreement\Exception\ExceptionInterface; 10 | 11 | class PermeableFilter extends AbstractFilter 12 | { 13 | /** 14 | * @param mixed $data 15 | * @return null|mixed 16 | * @throws ExceptionInterface 17 | */ 18 | public function filter($data) 19 | { 20 | return $data; 21 | } 22 | } -------------------------------------------------------------------------------- /source/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 4 | * @since: 2015-04-24 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv; 8 | 9 | use InvalidArgumentException as ParentClass; 10 | 11 | class InvalidArgumentException extends ParentClass {} 12 | -------------------------------------------------------------------------------- /source/Reader/EasyCsvReaderAdapter.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-16 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Reader; 8 | 9 | /** 10 | * Class EasyCsvReaderAdapter 11 | * @package Net\Bazzline\Component\Csv\Reader 12 | */ 13 | class EasyCsvReaderAdapter 14 | { 15 | /** @var ReaderInterface */ 16 | private $reader; 17 | 18 | /** 19 | * @param string $path 20 | * @param string $mode - not in use 21 | * @param bool $headersInFirstRow 22 | * @param ReaderInterface $reader - optional 23 | */ 24 | public function __construct($path, $mode = 'r+', $headersInFirstRow = true, ReaderInterface $reader = null) 25 | { 26 | if (is_null($reader)) { 27 | $factory = new ReaderFactory(); 28 | $this->reader = $factory->create(); 29 | } else { 30 | $this->reader = $reader; 31 | } 32 | 33 | $this->reader->setDelimiter(','); 34 | $this->reader->setEnclosure('"'); 35 | $this->reader->setPath($path); 36 | 37 | if ($headersInFirstRow) { 38 | $this->reader->enableHasHeadline(); 39 | } else { 40 | $this->reader->disableHasHeadline(); 41 | } 42 | } 43 | 44 | /** 45 | * @param string $delimiter 46 | */ 47 | public function setDelimiter($delimiter) 48 | { 49 | $this->reader->setDelimiter($delimiter); 50 | } 51 | 52 | /** 53 | * @param string $enclosure 54 | */ 55 | public function setEnclosure($enclosure) 56 | { 57 | $this->reader->setEnclosure($enclosure); 58 | } 59 | 60 | /** 61 | * @return array|false 62 | */ 63 | public function getHeaders() 64 | { 65 | $headline = $this->reader->readHeadline(); 66 | 67 | return $headline; 68 | } 69 | 70 | /** 71 | * @return array|bool|string 72 | */ 73 | public function getRow() 74 | { 75 | $this->reader->disableAddHeadlineToOutput(); 76 | 77 | return $this->reader->readOne(); 78 | } 79 | 80 | /** 81 | * @return array 82 | */ 83 | public function getAll() 84 | { 85 | $this->reader->enableAddHeadlineToOutput(); 86 | 87 | return $this->reader->readAll(); 88 | } 89 | 90 | /** 91 | * @return int|null 92 | */ 93 | public function getLineNumber() 94 | { 95 | return $this->reader->key(); 96 | } 97 | } -------------------------------------------------------------------------------- /source/Reader/FilteredReader.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-14 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Reader; 8 | 9 | use Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface; 10 | 11 | class FilteredReader extends Reader 12 | { 13 | /** @var FilterableInterface */ 14 | private $filter; 15 | 16 | /** 17 | * @param FilterableInterface $filter 18 | */ 19 | public function setFilter(FilterableInterface $filter) 20 | { 21 | $this->filter = $filter; 22 | } 23 | 24 | /** 25 | * @param null|int $lineNumber - if "null", current line number is used 26 | * @return array|bool|string 27 | */ 28 | public function readOne($lineNumber = null) 29 | { 30 | $data = parent::readOne($lineNumber); 31 | $filteredData = $this->filter->filter($data); 32 | 33 | if (is_null($filteredData)) { 34 | $filteredData = ($this->valid()) ? $this->readOne() : false; 35 | } 36 | 37 | return $filteredData; 38 | } 39 | } -------------------------------------------------------------------------------- /source/Reader/FilteredReaderFactory.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-06-24 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Reader; 8 | 9 | use Net\Bazzline\Component\Csv\Filter\PermeableFilter; 10 | use Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface; 11 | 12 | class FilteredReaderFactory extends ReaderFactory 13 | { 14 | /** 15 | * @return FilteredReader|ReaderInterface 16 | */ 17 | protected function getReader() 18 | { 19 | $reader = new FilteredReader(); 20 | 21 | $reader->setFilter($this->getFilter()); 22 | 23 | return $reader; 24 | } 25 | 26 | /** 27 | * @return FilterableInterface 28 | */ 29 | protected function getFilter() 30 | { 31 | return new PermeableFilter(); 32 | } 33 | } -------------------------------------------------------------------------------- /source/Reader/Reader.php: -------------------------------------------------------------------------------- 1 | 4 | * @since: 2015-04-17 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Reader; 8 | 9 | //@see https://github.com/ajgarlag/AjglCsv/blob/master/Reader/ReaderAbstract.php 10 | //@see https://github.com/jwage/easy-csv/blob/master/lib/EasyCSV/Reader.php 11 | //@todo implement save version to call enable/disable headline before setDelimiter etc. 12 | use Net\Bazzline\Component\Csv\AbstractBase; 13 | use Net\Bazzline\Component\Csv\InvalidArgumentException; 14 | use Net\Bazzline\Component\Toolbox\HashMap\Combine; 15 | use SplFileObject; 16 | 17 | class Reader extends AbstractBase implements ReaderInterface 18 | { 19 | /** @var bool */ 20 | private $addHeadlineToOutput = true; 21 | 22 | /** @var Combine */ 23 | private $combine; 24 | 25 | /** @var int */ 26 | private $initialLineNumber = 0; 27 | 28 | /** 29 | * @param null $currentLineNumber 30 | * @return array|bool|string 31 | */ 32 | public function __invoke($currentLineNumber = null) 33 | { 34 | return $this->readOne($currentLineNumber); 35 | } 36 | 37 | //begin of AbstractBase 38 | /** 39 | * @param string $delimiter 40 | * @throws InvalidArgumentException 41 | */ 42 | public function setDelimiter($delimiter) 43 | { 44 | parent::setDelimiter($delimiter); 45 | if ($this->hasHeadline()) { 46 | $this->enableHasHeadline(); 47 | } 48 | } 49 | 50 | /** 51 | * @param string $enclosure 52 | * @throws InvalidArgumentException 53 | */ 54 | public function setEnclosure($enclosure) 55 | { 56 | parent::setEnclosure($enclosure); 57 | if ($this->hasHeadline()) { 58 | $this->enableHasHeadline(); 59 | } 60 | } 61 | 62 | /** 63 | * @param string $escapeCharacter 64 | * @throws InvalidArgumentException 65 | */ 66 | public function setEscapeCharacter($escapeCharacter) 67 | { 68 | parent::setEscapeCharacter($escapeCharacter); 69 | if ($this->hasHeadline()) { 70 | $this->enableHasHeadline(); 71 | } 72 | } 73 | 74 | //end of AbstractBase 75 | 76 | //begin of Iterator 77 | /** 78 | * (PHP 5 >= 5.0.0)
79 | * Return the current element 80 | * @link http://php.net/manual/en/iterator.current.php 81 | * @return mixed Can return any type. 82 | */ 83 | public function current() 84 | { 85 | return $this->getFileHandler()->current(); 86 | } 87 | 88 | /** 89 | * (PHP 5 >= 5.0.0)
90 | * Move forward to next element 91 | * @link http://php.net/manual/en/iterator.next.php 92 | * @return void Any returned value is ignored. 93 | */ 94 | public function next() 95 | { 96 | $this->getFileHandler()->next(); 97 | } 98 | 99 | /** 100 | * (PHP 5 >= 5.0.0)
101 | * Return the key of the current element 102 | * @link http://php.net/manual/en/iterator.key.php 103 | * @return mixed scalar on success, or null on failure. 104 | */ 105 | public function key() 106 | { 107 | return $this->getFileHandler()->key(); 108 | } 109 | 110 | /** 111 | * (PHP 5 >= 5.0.0)
112 | * Checks if current position is valid 113 | * @link http://php.net/manual/en/iterator.valid.php 114 | * @return boolean The return value will be casted to boolean and then evaluated. 115 | * Returns true on success or false on failure. 116 | */ 117 | public function valid() 118 | { 119 | return $this->getFileHandler()->valid(); 120 | } 121 | 122 | /** 123 | * (PHP 5 >= 5.0.0)
124 | * Rewind the Iterator to the first element 125 | * @link http://php.net/manual/en/iterator.rewind.php 126 | * @return void Any returned value is ignored. 127 | */ 128 | public function rewind() 129 | { 130 | if ($this->hasHeadline()) { 131 | $this->updateHeadline(); 132 | $lineNumber = 1; 133 | } else { 134 | $lineNumber = 0; 135 | } 136 | $this->initialLineNumber = $lineNumber; 137 | $this->seekFileToCurrentLineNumberIfNeeded( 138 | $this->getFileHandler(), 139 | $lineNumber 140 | ); 141 | } 142 | //end of Iterator 143 | 144 | //begin of headlines 145 | /** 146 | * @return $this 147 | */ 148 | public function disableAddHeadlineToOutput() 149 | { 150 | $this->addHeadlineToOutput = false; 151 | 152 | return $this; 153 | } 154 | 155 | /** 156 | * @return $this 157 | */ 158 | public function enableAddHeadlineToOutput() 159 | { 160 | $this->addHeadlineToOutput = true; 161 | 162 | return $this; 163 | } 164 | 165 | /** 166 | * @return $this 167 | */ 168 | public function disableHasHeadline() 169 | { 170 | $this->resetHeadline(); 171 | $this->rewind(); 172 | 173 | return $this; 174 | } 175 | 176 | /** 177 | * @return $this 178 | */ 179 | public function enableHasHeadline() 180 | { 181 | $this->updateHeadline(); 182 | $this->rewind(); 183 | 184 | return $this; 185 | } 186 | 187 | private function updateHeadline() 188 | { 189 | $this->initialLineNumber = 0; 190 | $wasEnabled = $this->addHeadlineToOutput; 191 | 192 | if ($wasEnabled) { 193 | $this->disableAddHeadlineToOutput(); 194 | } 195 | $this->setHeadline($this->readOne(0)); 196 | if ($wasEnabled) { 197 | $this->enableAddHeadlineToOutput(); 198 | } 199 | } 200 | 201 | /** 202 | * @return false|array 203 | */ 204 | public function readHeadline() 205 | { 206 | return $this->getHeadline(); 207 | } 208 | //end of headlines 209 | 210 | //begin of general 211 | /** 212 | * @param Combine $combine 213 | * @return $this 214 | */ 215 | public function setCombine(Combine $combine) 216 | { 217 | $this->combine = $combine; 218 | 219 | return $this; 220 | } 221 | 222 | /** 223 | * @param null|int $lineNumber - if "null", current line number is used 224 | * @return array|bool|string 225 | */ 226 | public function readOne($lineNumber = null) 227 | { 228 | $file = $this->getFileHandler(); 229 | $headline = $this->getHeadline(); 230 | $hasHeadline = $this->hasHeadline(); 231 | $this->seekFileToCurrentLineNumberIfNeeded($file, $lineNumber); 232 | 233 | $addHeadline = ($hasHeadline && $this->addHeadlineToOutput && ($this->current() !== false)); 234 | $content = ($addHeadline) 235 | ? $this->combine->combine($headline, $this->current()) 236 | : $this->current(); 237 | $this->next(); 238 | 239 | return $content; 240 | } 241 | 242 | /** 243 | * @param int $length 244 | * @param null|int $lineNumberToStartWith - if "null", current line number is used 245 | * @return array 246 | */ 247 | public function readMany($length, $lineNumberToStartWith = null) 248 | { 249 | $this->rewind(); 250 | $lastLine = $lineNumberToStartWith + $length; 251 | $lines = []; 252 | $currentLine = $lineNumberToStartWith; 253 | 254 | //foreach not usable here since it is calling rewind before iterating 255 | while ($currentLine < $lastLine) { 256 | $line = $this->readOne($currentLine); 257 | $lines = $this->addToLinesIfLineIsValid($lines, $line); 258 | if (!$this->valid()) { 259 | $currentLine = $lastLine; 260 | } 261 | ++$currentLine; 262 | } 263 | 264 | return $lines; 265 | } 266 | 267 | /** 268 | * @return array 269 | */ 270 | public function readAll() 271 | { 272 | $this->rewind(); 273 | $lines = []; 274 | 275 | while ($line = $this()) { 276 | $lines = $this->addToLinesIfLineIsValid($lines, $line); 277 | } 278 | 279 | return $lines; 280 | } 281 | //end of general 282 | 283 | /** 284 | * @return string 285 | */ 286 | protected function getFileHandlerOpenMode() 287 | { 288 | return 'r'; 289 | } 290 | 291 | /** 292 | * @param array $lines 293 | * @param mixed $line 294 | * @return array 295 | */ 296 | private function addToLinesIfLineIsValid(array &$lines, $line) 297 | { 298 | if (!is_null($line)) { 299 | $lines[] = $line; 300 | } 301 | 302 | return $lines; 303 | } 304 | 305 | /** 306 | * @param SplFileObject $file 307 | * @param null|int $newLineNumber 308 | * @return SplFileObject 309 | */ 310 | private function seekFileToCurrentLineNumberIfNeeded(SplFileObject $file, $newLineNumber = null) 311 | { 312 | $seekIsNeeded = ((!is_null($newLineNumber)) 313 | && ($newLineNumber >= $this->initialLineNumber) 314 | && ($newLineNumber !== $this->key())); 315 | 316 | if ($seekIsNeeded) { 317 | $file->seek($newLineNumber); 318 | } 319 | 320 | return $file; 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /source/Reader/ReaderFactory.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-06 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Reader; 8 | 9 | use Net\Bazzline\Component\Csv\AbstractFactory; 10 | use Net\Bazzline\Component\Toolbox\HashMap\Combine; 11 | 12 | class ReaderFactory extends AbstractFactory 13 | { 14 | /** 15 | * @return object|Reader|ReaderInterface 16 | */ 17 | public function create() 18 | { 19 | $reader = $this->getReader(); 20 | 21 | $reader->setCombine($this->getCombine()); 22 | $reader->setDelimiter($this->getDelimiter()); 23 | $reader->setEnclosure($this->getEnclosure()); 24 | $reader->setEscapeCharacter($this->getEscapeCharacter()); 25 | 26 | return $reader; 27 | } 28 | 29 | /** 30 | * @return Combine 31 | */ 32 | protected function getCombine() 33 | { 34 | return new Combine(); 35 | } 36 | 37 | /** 38 | * @return Reader|ReaderInterface 39 | */ 40 | protected function getReader() 41 | { 42 | return new Reader(); 43 | } 44 | } -------------------------------------------------------------------------------- /source/Reader/ReaderInterface.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-09-09 5 | */ 6 | namespace Net\Bazzline\Component\Csv\Reader; 7 | 8 | use Iterator; 9 | use Net\Bazzline\Component\Csv\BaseInterface; 10 | use Net\Bazzline\Component\Toolbox\HashMap\Combine; 11 | 12 | /** 13 | * Interface ReaderInterface 14 | * @package Net\Bazzline\Component\Csv\Reader 15 | */ 16 | interface ReaderInterface extends BaseInterface, Iterator 17 | { 18 | /** 19 | * @return $this 20 | */ 21 | public function disableAddHeadlineToOutput(); 22 | 23 | /** 24 | * @return $this 25 | */ 26 | public function enableAddHeadlineToOutput(); 27 | 28 | /** 29 | * @return $this 30 | */ 31 | public function disableHasHeadline(); 32 | 33 | /** 34 | * @return $this 35 | */ 36 | public function enableHasHeadline(); 37 | 38 | /** 39 | * @return false|array 40 | */ 41 | public function readHeadline(); 42 | 43 | /** 44 | * @param Combine $combine 45 | * @return $this 46 | */ 47 | public function setCombine(Combine $combine); 48 | 49 | /** 50 | * @param null|int $lineNumber - if "null", current line number is used 51 | * @return array|bool|string 52 | */ 53 | public function readOne($lineNumber = null); 54 | 55 | /** 56 | * @param int $length 57 | * @param null|int $lineNumberToStartWith - if "null", current line number is used 58 | * @return array 59 | */ 60 | public function readMany($length, $lineNumberToStartWith = null); 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function readAll(); 66 | } -------------------------------------------------------------------------------- /source/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 4 | * @since: 2015-04-17 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv; 8 | 9 | use RuntimeException as ParentClass; 10 | 11 | class RuntimeException extends ParentClass {} -------------------------------------------------------------------------------- /source/Writer/EasyCsvWriterAdapter.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-16 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Writer; 8 | 9 | class EasyCsvWriterAdapter 10 | { 11 | /** @var Writer|WriterForPhp5Dot3|WriterInterface */ 12 | private $writer; 13 | 14 | /** 15 | * @param string $path 16 | * @param string $mode - is not used 17 | * @param null|WriterInterface $writer - optional 18 | */ 19 | public function __construct($path, $mode = 'r+', WriterInterface $writer = null) 20 | { 21 | if (is_null($writer)) { 22 | $factory = new WriterFactory(); 23 | $this->writer = $factory->create(); 24 | } else { 25 | $this->writer = $writer; 26 | } 27 | 28 | $this->writer->setDelimiter(','); 29 | $this->writer->setEnclosure('"'); 30 | $this->writer->setPath($path); 31 | } 32 | 33 | /** 34 | * @param string $delimiter 35 | */ 36 | public function setDelimiter($delimiter) 37 | { 38 | $this->writer->setDelimiter($delimiter); 39 | } 40 | 41 | /** 42 | * @param string $enclosure 43 | */ 44 | public function setEnclosure($enclosure) 45 | { 46 | $this->writer->setEnclosure($enclosure); 47 | } 48 | 49 | /** 50 | * @param mixed $row 51 | * @return false|int 52 | */ 53 | public function writeRow($row) 54 | { 55 | return $this->writer->writeOne($row); 56 | } 57 | 58 | /** 59 | * @param array $array 60 | */ 61 | public function writeFromArray(array $array) 62 | { 63 | $this->writer->writeMany($array); 64 | } 65 | } -------------------------------------------------------------------------------- /source/Writer/FilteredWriter.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-14 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Writer; 8 | 9 | use Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface; 10 | 11 | class FilteredWriter extends Writer 12 | { 13 | /** @var FilterableInterface */ 14 | private $filter; 15 | 16 | /** 17 | * @param FilterableInterface $filter 18 | */ 19 | public function setFilter(FilterableInterface $filter) 20 | { 21 | $this->filter = $filter; 22 | } 23 | 24 | /** 25 | * @param array|mixed $data 26 | * @return false|int 27 | */ 28 | public function writeOne($data) 29 | { 30 | $filteredData = $this->filter->filter($data); 31 | 32 | return (!is_null($filteredData)) 33 | ? parent::writeOne($filteredData) : false; 34 | } 35 | } -------------------------------------------------------------------------------- /source/Writer/FilteredWriterFactory.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-14 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Writer; 8 | 9 | use Net\Bazzline\Component\Csv\Filter\PermeableFilter; 10 | use Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface; 11 | 12 | class FilteredWriterFactory extends WriterFactory 13 | { 14 | /** 15 | * @return FilteredWriter|FilteredWriterForPhp3Dot3 16 | */ 17 | protected function getWriter() 18 | { 19 | if ($this->phpVersionLessThen5Dot4()) { 20 | $writer = new FilteredWriterForPhp3Dot3(); 21 | } else { 22 | $writer = new FilteredWriter(); 23 | } 24 | 25 | $writer->setFilter($this->getFilter()); 26 | 27 | return $writer; 28 | } 29 | 30 | /** 31 | * @return FilterableInterface 32 | */ 33 | protected function getFilter() 34 | { 35 | return new PermeableFilter(); 36 | } 37 | } -------------------------------------------------------------------------------- /source/Writer/FilteredWriterForPhp3Dot3.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-14 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Writer; 8 | 9 | use Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface; 10 | 11 | class FilteredWriterForPhp3Dot3 extends WriterForPhp5Dot3 12 | { 13 | /** @var FilterableInterface */ 14 | private $filter; 15 | 16 | /** 17 | * @param FilterableInterface $filter 18 | */ 19 | public function setFilter(FilterableInterface $filter) 20 | { 21 | $this->filter = $filter; 22 | } 23 | 24 | /** 25 | * @param array|mixed $data 26 | * @return false|int 27 | */ 28 | public function writeOne($data) 29 | { 30 | $filteredData = $this->filter->filter($data); 31 | 32 | return (!is_null($filteredData)) 33 | ? parent::writeOne($filteredData) : false; 34 | } 35 | } -------------------------------------------------------------------------------- /source/Writer/Writer.php: -------------------------------------------------------------------------------- 1 | 4 | * @since: 2015-04-17 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Writer; 8 | 9 | use Net\Bazzline\Component\Csv\AbstractBase; 10 | use Net\Bazzline\Component\Csv\InvalidArgumentException; 11 | 12 | /** 13 | * Class Writer 14 | * @package Net\Bazzline\Component\Csv\Writer 15 | */ 16 | class Writer extends AbstractBase implements WriterInterface 17 | { 18 | const OPEN_MODE_APPEND = 'a'; 19 | const OPEN_MODE_TRUNCATE = 'w'; 20 | 21 | /** @var boolean */ 22 | private $useTruncateAsOpenMode = false; 23 | 24 | /** 25 | * @param mixed|array $data 26 | * @return false|int 27 | */ 28 | public function __invoke($data) 29 | { 30 | return $this->writeOne($data); 31 | } 32 | 33 | 34 | //begin of general 35 | /** 36 | * @param string $path 37 | * @param bool $setPathAsCurrentPath 38 | * @return bool 39 | * @throws InvalidArgumentException 40 | * @todo implement path validation 41 | */ 42 | public function copy($path, $setPathAsCurrentPath = false) 43 | { 44 | $couldBeCopied = copy($this->getPath(), $path); 45 | 46 | if ($setPathAsCurrentPath) { 47 | if ($couldBeCopied) { 48 | $this->close(); 49 | $this->setPath($path); 50 | } 51 | } 52 | 53 | return $couldBeCopied; 54 | } 55 | 56 | /** 57 | * @param string $path 58 | * @return bool 59 | * @todo implement path validation 60 | */ 61 | public function move($path) 62 | { 63 | $couldBeMoved = rename($this->getPath(), $path); 64 | 65 | if ($couldBeMoved) { 66 | $this->close(); 67 | $this->setPath($path); 68 | } 69 | 70 | return $couldBeMoved; 71 | } 72 | 73 | /** 74 | * @return bool 75 | */ 76 | public function delete() 77 | { 78 | $this->close(); 79 | 80 | return unlink($this->getPath()); 81 | } 82 | 83 | public function truncate() 84 | { 85 | $this->close(); 86 | $this->useTruncateAsOpenMode = true; 87 | $this->open($this->getPath()); 88 | $this->useTruncateAsOpenMode = false; 89 | } 90 | 91 | /** 92 | * truncates file and writes content 93 | * 94 | * @param array $collection 95 | * @return false|int 96 | */ 97 | public function writeAll(array $collection) 98 | { 99 | $this->truncate(); 100 | 101 | return $this->writeMany($collection); 102 | } 103 | 104 | /** 105 | * @param array $headlines 106 | * @return false|int 107 | */ 108 | public function writeHeadlines(array $headlines) 109 | { 110 | $this->setHeadline($headlines); 111 | 112 | return $this->writeOne($headlines); 113 | } 114 | 115 | /** 116 | * @param array $collection 117 | * @return false|int 118 | */ 119 | public function writeMany(array $collection) 120 | { 121 | $lengthOfTheWrittenStrings = 0; 122 | 123 | foreach ($collection as $data) { 124 | $lengthOfTheWrittenString = $this->writeOne($data); 125 | 126 | if ($lengthOfTheWrittenString === false) { 127 | $lengthOfTheWrittenStrings = $lengthOfTheWrittenString; 128 | break; 129 | } else { 130 | $lengthOfTheWrittenStrings += $lengthOfTheWrittenString; 131 | } 132 | } 133 | 134 | return $lengthOfTheWrittenStrings; 135 | } 136 | 137 | /** 138 | * @param string|array $data 139 | * @return false|int 140 | */ 141 | public function writeOne($data) 142 | { 143 | $data = $this->convertToArrayIfNeeded($data); 144 | 145 | return $this->getFileHandler()->fputcsv($data, $this->getDelimiter(), $this->getEnclosure()); 146 | } 147 | //end of general 148 | 149 | /** 150 | * @param string|array $data 151 | * @return array 152 | */ 153 | protected function convertToArrayIfNeeded($data) 154 | { 155 | if (!is_array($data)) { 156 | $data = explode($this->getDelimiter(), $data); 157 | $data = array_map(function($value) { 158 | return trim($value); 159 | }, $data); 160 | } 161 | 162 | return $data; 163 | } 164 | 165 | /** 166 | * @return string 167 | */ 168 | protected function getFileHandlerOpenMode() 169 | { 170 | return ($this->useTruncateAsOpenMode) ? self::OPEN_MODE_TRUNCATE : self::OPEN_MODE_APPEND; 171 | } 172 | } -------------------------------------------------------------------------------- /source/Writer/WriterFactory.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-06 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Writer; 8 | 9 | use Net\Bazzline\Component\Csv\AbstractFactory; 10 | 11 | class WriterFactory extends AbstractFactory 12 | { 13 | /** 14 | * @return Writer|WriterForPhp5Dot3|WriterInterface 15 | */ 16 | public function create() 17 | { 18 | $writer = $this->getWriter(); 19 | 20 | $writer->setDelimiter($this->getDelimiter()); 21 | $writer->setEnclosure($this->getEnclosure()); 22 | $writer->setEscapeCharacter($this->getEscapeCharacter()); 23 | 24 | return $writer; 25 | } 26 | 27 | /** 28 | * @return Writer|WriterForPhp5Dot3|WriterInterface 29 | */ 30 | protected function getWriter() 31 | { 32 | if ($this->phpVersionLessThen5Dot4()) { 33 | $writer = new WriterForPhp5Dot3(); 34 | } else { 35 | $writer = new Writer(); 36 | } 37 | 38 | return $writer; 39 | } 40 | 41 | /** 42 | * @return boolean 43 | */ 44 | protected function phpVersionLessThen5Dot4() 45 | { 46 | return (version_compare(phpversion(), '5.4', '<')); 47 | } 48 | } -------------------------------------------------------------------------------- /source/Writer/WriterForPhp5Dot3.php: -------------------------------------------------------------------------------- 1 | 4 | * @since: 2015-05-13 5 | */ 6 | 7 | namespace Net\Bazzline\Component\Csv\Writer; 8 | 9 | /** 10 | * Class WriterForPhp5Dot3 11 | * @package Net\Bazzline\Component\Csv\Writer 12 | */ 13 | class WriterForPhp5Dot3 extends Writer 14 | { 15 | public function __destruct() 16 | { 17 | $this->close(); 18 | } 19 | 20 | /** 21 | * @param mixed|array $data 22 | * @return false|int 23 | */ 24 | public function writeOne($data) 25 | { 26 | $data = $this->convertToArrayIfNeeded($data); 27 | 28 | return fputcsv($this->getFileHandler(), $data, $this->getDelimiter(), $this->getEnclosure()); 29 | } 30 | 31 | /** 32 | * @param string $path 33 | * @return resource 34 | */ 35 | protected function open($path) 36 | { 37 | $fileHandler = fopen($path, $this->getFileHandlerOpenMode()); 38 | 39 | return $fileHandler; 40 | } 41 | } -------------------------------------------------------------------------------- /source/Writer/WriterInterface.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-09-09 5 | */ 6 | namespace Net\Bazzline\Component\Csv\Writer; 7 | 8 | use Net\Bazzline\Component\Csv\BaseInterface; 9 | 10 | /** 11 | * Interface WriterInterface 12 | * @package Net\Bazzline\Component\Csv\Writer 13 | */ 14 | interface WriterInterface extends BaseInterface 15 | { 16 | /** 17 | * @param mixed|array $data 18 | * @return false|int 19 | */ 20 | public function __invoke($data); 21 | 22 | 23 | /** 24 | * @param string $path 25 | * @param bool $setPathAsCurrentPath 26 | * @return bool 27 | * @throws \InvalidArgumentException 28 | */ 29 | public function copy($path, $setPathAsCurrentPath = false); 30 | 31 | /** 32 | * @param string $path 33 | * @return bool 34 | */ 35 | public function move($path); 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function delete(); 41 | 42 | public function truncate(); 43 | 44 | /** 45 | * truncates file and writes content 46 | * 47 | * @param array $collection 48 | * @return false|int 49 | */ 50 | public function writeAll(array $collection); 51 | 52 | /** 53 | * @param array $headlines 54 | * @return false|int 55 | */ 56 | public function writeHeadlines(array $headlines); 57 | 58 | /** 59 | * @param array $collection 60 | * @return false|int 61 | */ 62 | public function writeMany(array $collection); 63 | 64 | /** 65 | * @param string|array $data 66 | * @return false|int 67 | */ 68 | public function writeOne($data); 69 | } -------------------------------------------------------------------------------- /test/AbstractTestCase.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-04-24 5 | */ 6 | 7 | namespace Test\Net\Bazzline\Component\Csv; 8 | 9 | use Mockery; 10 | use Net\Bazzline\Component\Csv\Reader\FilteredReader; 11 | use Net\Bazzline\Component\Csv\Reader\FilteredReaderFactory; 12 | use Net\Bazzline\Component\Csv\Reader\Reader; 13 | use Net\Bazzline\Component\Csv\Reader\ReaderFactory; 14 | use Net\Bazzline\Component\Csv\Reader\ReaderInterface; 15 | use Net\Bazzline\Component\Csv\Writer\FilteredWriter; 16 | use Net\Bazzline\Component\Csv\Writer\FilteredWriterFactory; 17 | use Net\Bazzline\Component\Csv\Writer\Writer; 18 | use Net\Bazzline\Component\Csv\Writer\WriterFactory; 19 | use Net\Bazzline\Component\Csv\Writer\WriterInterface; 20 | use Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface; 21 | use org\bovigo\vfs\vfsStream; 22 | use PHPUnit\Framework\TestCase; 23 | 24 | abstract class AbstractTestCase extends TestCase 25 | { 26 | /** @var FilteredReaderFactory */ 27 | private $filteredReaderFactory; 28 | 29 | /** @var FilteredWriterFactory */ 30 | private $filteredWriterFactory; 31 | 32 | /** @var string */ 33 | private $path; 34 | 35 | /** @var array */ 36 | private $pathOfFiles; 37 | 38 | /** @var ReaderFactory */ 39 | private $readerFactory; 40 | 41 | /** @var WriterFactory */ 42 | private $writerFactory; 43 | 44 | /** 45 | * Constructs a test case with the given name. 46 | * 47 | * @param string $name 48 | * @param array $data 49 | * @param string $dataName 50 | */ 51 | public function __construct($name = null, array $data = [], $dataName = '') 52 | { 53 | parent::__construct($name, $data, $dataName); 54 | 55 | $this->filteredReaderFactory = new FilteredReaderFactory(); 56 | $this->filteredWriterFactory = new FilteredWriterFactory(); 57 | $this->path = __DIR__ . DIRECTORY_SEPARATOR . 'data'; 58 | $this->pathOfFiles = []; 59 | $this->readerFactory = new ReaderFactory(); 60 | $this->writerFactory = new WriterFactory(); 61 | } 62 | 63 | public function __destruct() 64 | { 65 | foreach ($this->pathOfFiles as $path) { 66 | if (file_exists($path)) { 67 | unlink($path); 68 | } 69 | } 70 | Mockery::close(); 71 | } 72 | 73 | /** 74 | * @param int $permissions 75 | * @param string $path 76 | * @return \org\bovigo\vfs\vfsStreamDirectory 77 | */ 78 | protected function createFilesystem($permissions = 0700, $path = 'root') 79 | { 80 | return vfsStream::setup($path, $permissions); 81 | } 82 | 83 | /** 84 | * @param string $name 85 | * @param int $permissions 86 | * @return \org\bovigo\vfs\vfsStreamFile 87 | */ 88 | protected function createFile($name = 'test.csv', $permissions = 0700) 89 | { 90 | return vfsStream::newFile($name, $permissions); 91 | } 92 | 93 | /** 94 | * @param string $name 95 | * @return string 96 | */ 97 | protected function createRealFilePath($name) 98 | { 99 | $path = $this->path . DIRECTORY_SEPARATOR . $name; 100 | $this->pathOfFiles[] = $path; 101 | 102 | return $path; 103 | } 104 | 105 | /** 106 | * @return FilteredReader 107 | */ 108 | protected function createFilteredReader() 109 | { 110 | return $this->filteredReaderFactory->create(); 111 | } 112 | 113 | /** 114 | * @return ReaderInterface 115 | */ 116 | protected function createReader() 117 | { 118 | return $this->readerFactory->create(); 119 | } 120 | 121 | 122 | 123 | /** 124 | * @return \Mockery\MockInterface|FilterableInterface 125 | */ 126 | protected function createFilter() 127 | { 128 | return Mockery::mock('Net\Bazzline\Component\GenericAgreement\Data\FilterableInterface'); 129 | } 130 | 131 | /** 132 | * @return FilteredWriter 133 | */ 134 | protected function createFilteredWriter() 135 | { 136 | return $this->filteredWriterFactory->create(); 137 | } 138 | 139 | /** 140 | * @return Writer|WriterInterface 141 | */ 142 | protected function createWriter() 143 | { 144 | return $this->writerFactory->create(); 145 | } 146 | 147 | /** 148 | * @return boolean 149 | */ 150 | protected function phpVersionLessThen5Dot4() 151 | { 152 | return (version_compare(phpversion(), '5.4', '<')); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /test/EasyCsvReaderAdapterTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-17 5 | */ 6 | 7 | namespace Test\Net\Bazzline\Component\Csv; 8 | 9 | use Net\Bazzline\Component\Csv\Reader\EasyCsvReaderAdapter; 10 | 11 | /** 12 | * Class EasyCsvReaderAdapterTest 13 | * @package Test\Net\Bazzline\Component\Csv 14 | * @see https://github.com/jwage/easy-csv/blob/0.0.1/tests/EasyCSV/Tests/ReaderTest.php 15 | */ 16 | class EasyCsvReaderAdapterTest extends AbstractTestCase 17 | { 18 | /** 19 | * @dataProvider getReaders 20 | * @param EasyCsvReaderAdapter $reader 21 | */ 22 | public function testOneAtATime(EasyCsvReaderAdapter $reader) 23 | { 24 | while ($row = $reader->getRow()) { 25 | $this->assertTrue(is_array($row)); 26 | $this->assertEquals(3, count($row)); 27 | } 28 | } 29 | 30 | /** 31 | * @dataProvider getReaders 32 | * @param EasyCsvReaderAdapter $reader 33 | */ 34 | public function testGetAll(EasyCsvReaderAdapter $reader) 35 | { 36 | $this->assertEquals(5, count($reader->getAll())); 37 | } 38 | 39 | /** 40 | * @dataProvider getReaders 41 | * @param EasyCsvReaderAdapter $reader 42 | */ 43 | public function testGetHeaders(EasyCsvReaderAdapter $reader) 44 | { 45 | $this->assertEquals( 46 | [ 47 | "column1", 48 | "column2", 49 | "column3" 50 | ], 51 | $reader->getHeaders() 52 | ); 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function getReaders() 59 | { 60 | $content = 61 | '"column1", "column2", "column3"' . PHP_EOL . 62 | '"1column2value", "1column3value", "1column4value"' . PHP_EOL . 63 | '"2column2value", "2column3value", "2column4value"' . PHP_EOL . 64 | '"3column2value", "3column3value", "3column4value"' . PHP_EOL . 65 | '"4column2value", "4column3value", "4column4value"' . PHP_EOL . 66 | '"5column2value", "5column3value", "5column4value"'; 67 | $contentWithSemicolonAsDelimiter = 68 | '"column1"; "column2"; "column3"' . PHP_EOL . 69 | '"1column2value"; "1column3value"; "1column4value"' . PHP_EOL . 70 | '"2column2value"; "2column3value"; "2column4value"' . PHP_EOL . 71 | '"3column2value"; "3column3value"; "3column4value"' . PHP_EOL . 72 | '"4column2value"; "4column3value"; "4column4value"' . PHP_EOL . 73 | '5column2value"; "5column3value"; "5column4value"'; 74 | 75 | if ($this->phpVersionLessThen5Dot4()) { 76 | $path = $this->createRealFilePath('read.csv'); 77 | $pathWithSemicolonAsDelimiter = $this->createRealFilePath('read_cs.csv'); 78 | 79 | file_put_contents($path, $content); 80 | file_put_contents($pathWithSemicolonAsDelimiter, $contentWithSemicolonAsDelimiter); 81 | 82 | $reader = new EasyCsvReaderAdapter($path); 83 | $readerWithSemicolonAsDelimiter = new EasyCsvReaderAdapter($pathWithSemicolonAsDelimiter); 84 | } else { 85 | $file = $this->createFile('read.csv'); 86 | $filesystem = $this->createFilesystem(); 87 | $fileWithSemicolonAsDelimiter = $this->createFile('read_sc.csv'); 88 | 89 | $file->setContent($content); 90 | $fileWithSemicolonAsDelimiter->setContent($contentWithSemicolonAsDelimiter); 91 | $filesystem->addChild($file); 92 | $filesystem->addChild($fileWithSemicolonAsDelimiter); 93 | 94 | $reader = new EasyCsvReaderAdapter($file->url()); 95 | $readerWithSemicolonAsDelimiter = new EasyCsvReaderAdapter($fileWithSemicolonAsDelimiter->url()); 96 | } 97 | 98 | $readerWithSemicolonAsDelimiter->setDelimiter(';'); 99 | 100 | return [ 101 | [ 102 | $reader 103 | ], 104 | [ 105 | $readerWithSemicolonAsDelimiter 106 | ] 107 | ]; 108 | } 109 | 110 | 111 | public function testReadWrittenFile() 112 | { 113 | $content = 'column1,column2,column3' . PHP_EOL . 114 | '1test1,"1test2ing this out",1test3' . PHP_EOL . 115 | '2test1,"2test2 ing this out ok",2test3' . PHP_EOL; 116 | $file = $this->createFile('write.csv'); 117 | $filesystem = $this->createFilesystem(); 118 | 119 | $file->setContent($content); 120 | $filesystem->addChild($file); 121 | 122 | $reader = new EasyCsvReaderAdapter($file->url()); 123 | 124 | $results = $reader->getAll(); 125 | $expected = [ 126 | [ 127 | 'column1' => '1test1', 128 | 'column2' => '1test2ing this out', 129 | 'column3' => '1test3' 130 | ], 131 | [ 132 | 'column1' => '2test1', 133 | 'column2' => '2test2 ing this out ok', 134 | 'column3' => '2test3' 135 | ] 136 | ]; 137 | 138 | $this->assertEquals($expected, $results); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /test/EasyCsvWriterAdapterTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-17 5 | */ 6 | 7 | namespace Test\Net\Bazzline\Component\Csv; 8 | 9 | use Net\Bazzline\Component\Csv\Writer\EasyCsvWriterAdapter; 10 | 11 | /** 12 | * Class EasyCsvWriterAdapterTest 13 | * @package Test\Net\Bazzline\Component\Csv 14 | * @see https://github.com/jwage/easy-csv/blob/0.0.1/tests/EasyCSV/Tests/WriterTest.php 15 | */ 16 | class EasyCsvWriterAdapterTest extends AbstractTestCase 17 | { 18 | public function testWriteRow() 19 | { 20 | $file = $this->createFile('write.csv'); 21 | $filesystem = $this->createFilesystem(); 22 | $filesystem->addChild($file); 23 | $writer = new EasyCsvWriterAdapter($file->url()); 24 | 25 | $expectedContent = 'test1,test2,test3' . PHP_EOL; 26 | $line = 'test1, test2, test3'; 27 | 28 | $writer->writeRow($line); 29 | 30 | $this->assertEquals($expectedContent, $file->getContent()); 31 | } 32 | 33 | public function testWriteFromArray() 34 | { 35 | $file = $this->createFile('write.csv'); 36 | $filesystem = $this->createFilesystem(); 37 | $filesystem->addChild($file); 38 | $writer = new EasyCsvWriterAdapter($file->url()); 39 | 40 | $array = array( 41 | '1test1, 1test2ing this out, 1test3', 42 | array( 43 | '2test1', '2test2 ing this out ok', '2test3' 44 | ) 45 | ); 46 | $line = 'column1, column2, column3'; 47 | $expectedContent = 'column1,column2,column3' . PHP_EOL . 48 | '1test1,"1test2ing this out",1test3' . PHP_EOL . 49 | '2test1,"2test2 ing this out ok",2test3' . PHP_EOL; 50 | 51 | $writer->writeRow($line); 52 | $writer->writeFromArray($array); 53 | 54 | $this->assertEquals($expectedContent, $file->getContent()); 55 | } 56 | } -------------------------------------------------------------------------------- /test/FilteredReaderTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-06-22 5 | */ 6 | 7 | namespace Test\Net\Bazzline\Component\Csv; 8 | 9 | //@todo implement call of this tests with different delimiters etc. (after the 10 | //setters are developed 11 | //@todo implement writeOne(!array) 12 | class FilteredReaderTest extends ReaderTest 13 | { 14 | public function testReadContentWithAlwaysInvalidFilter() 15 | { 16 | $file = $this->createFile(); 17 | $filesystem = $this->createFilesystem(); 18 | $filter = $this->createFilter(); 19 | $reader = $this->createFilteredReader(); 20 | 21 | $file->setContent($this->getContentAsString()); 22 | $filesystem->addChild($file); 23 | $filter->shouldReceive('filter') 24 | ->andReturn(null); 25 | 26 | $reader->setFilter($filter); 27 | $reader->setPath($file->url()); 28 | 29 | $this->assertFalse($reader->readOne()); 30 | $this->assertFalse($reader()); 31 | $this->assertEquals([], $reader->readAll()); 32 | } 33 | 34 | public function testReadAllPassingSecondRowAsValidFilter() 35 | { 36 | $lineNumberOfContent = 1; 37 | $content = $this->contentAsArray; 38 | $expectedContent = [ 39 | $content[ 40 | $lineNumberOfContent 41 | ] 42 | ]; 43 | $file = $this->createFile(); 44 | $filesystem = $this->createFilesystem(); 45 | $filter = $this->createFilter(); 46 | $reader = $this->createFilteredReader(); 47 | 48 | $file->setContent($this->getContentAsString()); 49 | $filesystem->addChild($file); 50 | $filter->shouldReceive('filter') 51 | ->andReturn(null, $expectedContent[0], null, null); 52 | 53 | $reader->setFilter($filter); 54 | $reader->setPath($file->url()); 55 | 56 | $this->assertEquals($expectedContent, $reader->readAll()); 57 | } 58 | 59 | public function testReadManyPassingSecondRowAsValidFilter() 60 | { 61 | $content = $this->contentAsArray; 62 | $expectedContent = []; 63 | $length = 2; 64 | $file = $this->createFile(); 65 | $filesystem = $this->createFilesystem(); 66 | $filter = $this->createFilter(); 67 | $reader = $this->createFilteredReader(); 68 | $start = 2; 69 | 70 | $file->setContent($this->getContentAsString()); 71 | $filesystem->addChild($file); 72 | 73 | //generating expected content 74 | $end = $start + $length; 75 | $counter = ($start + 1); //+1 because of the first false from the filter 76 | 77 | while ($counter < $end) { 78 | $expectedContent[] = $content[$counter]; 79 | ++$counter; 80 | } 81 | 82 | $filter->shouldReceive('filter') 83 | ->andReturn(null, $expectedContent[0]); 84 | 85 | $reader->setFilter($filter); 86 | $reader->setPath($file->url()); 87 | 88 | $this->assertEquals($expectedContent, $reader->readMany($length, $start)); 89 | } 90 | 91 | public function testReadOnePassingSecondRowAsValidFilter() 92 | { 93 | $lineNumberOfContent = 1; 94 | $content = $this->contentAsArray; 95 | $expectedContent = $content[$lineNumberOfContent]; 96 | $file = $this->createFile(); 97 | $filesystem = $this->createFilesystem(); 98 | $filter = $this->createFilter(); 99 | $reader = $this->createFilteredReader(); 100 | 101 | $file->setContent($this->getContentAsString()); 102 | $filesystem->addChild($file); 103 | $filter->shouldReceive('filter') 104 | ->andReturn(null, $expectedContent); 105 | 106 | $reader->setFilter($filter); 107 | $reader->setPath($file->url()); 108 | 109 | $this->assertEquals($expectedContent, $reader()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/FilteredWriterTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-06-22 5 | */ 6 | 7 | namespace Test\Net\Bazzline\Component\Csv; 8 | 9 | //@todo implement call of this tests with different delimiters etc. (after the 10 | //setters are developed 11 | //@todo implement writeOne(!array) 12 | class FilteredWriterTest extends WriterTest 13 | { 14 | public function testWriteContentLinePerLineUsingWriteOneAndAlwaysInvalidFilter() 15 | { 16 | $delimiters = $this->delimiters; 17 | 18 | foreach ($delimiters as $delimiter) { 19 | $collection = $this->contentAsArray; 20 | $expectedContent = null; 21 | $filter = $this->createFilter(); 22 | $file = $this->createFile(); 23 | $filesystem = $this->createFilesystem(); 24 | $writer = $this->createFilteredWriter(); 25 | 26 | $filter->shouldReceive('filter') 27 | ->andReturn(null); 28 | $filesystem->addChild($file); 29 | 30 | $writer->setDelimiter($delimiter); 31 | $writer->setFilter($filter); 32 | $writer->setPath($file->url()); 33 | 34 | foreach ($collection as $content) { 35 | $this->assertFalse($writer->writeOne($content)); 36 | } 37 | 38 | $this->assertEquals($expectedContent, $file->getContent()); 39 | } 40 | } 41 | public function testWriteContentLinePerLineUsingWriteOneAndPassingSecondRowAsValidFilter() 42 | { 43 | $delimiters = $this->delimiters; 44 | $lineNumberOfContent = 1; 45 | 46 | foreach ($delimiters as $delimiter) { 47 | $collection = $this->contentAsArray; 48 | $expectedContent = $this->convertArrayToStrings( 49 | [ 50 | $collection[ 51 | $lineNumberOfContent 52 | ] 53 | ], 54 | $delimiter 55 | ); 56 | $filter = $this->createFilter(); 57 | $file = $this->createFile(); 58 | $filesystem = $this->createFilesystem(); 59 | $writer = $this->createFilteredWriter(); 60 | 61 | $filter->shouldReceive('filter') 62 | ->andReturn(null, $expectedContent, null, null); 63 | $filesystem->addChild($file); 64 | 65 | $writer->setDelimiter($delimiter); 66 | $writer->setFilter($filter); 67 | $writer->setPath($file->url()); 68 | 69 | foreach ($collection as $index => $content) { 70 | if ($index === $lineNumberOfContent) { 71 | $this->assertNotFalse($writer->writeOne($content)); 72 | } else { 73 | $this->assertFalse($writer->writeOne($content)); 74 | } 75 | } 76 | 77 | $this->assertEquals($expectedContent, $file->getContent()); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/ReaderTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-04-24 5 | */ 6 | 7 | namespace Test\Net\Bazzline\Component\Csv; 8 | 9 | //@todo implement call of this tests with different delimiters etc. (after the 10 | //setters are developed 11 | //@todo implement tests with headlines enabled for each situation 12 | class ReaderTest extends AbstractTestCase 13 | { 14 | /** 15 | * @var array 16 | */ 17 | protected $contentAsArray = [ 18 | [ 19 | 'headlines foo', 20 | 'headlines bar' 21 | ], 22 | [ 23 | 'foo', 24 | 'bar' 25 | ], 26 | [ 27 | 'foobar', 28 | 'baz' 29 | ], 30 | [ 31 | 'baz', 32 | 'barfoo' 33 | ] 34 | ]; 35 | 36 | public function testHasHeadline() 37 | { 38 | $content = $this->contentAsArray; 39 | $file = $this->createFile(); 40 | $filesystem = $this->createFilesystem(); 41 | $reader = $this->createReader(); 42 | 43 | $expectedContent = array_slice($content, 1); 44 | $expectedHeadline = $content[0]; 45 | $expectedContent = $this->addHeadlineAsKeysToContent($expectedHeadline, $expectedContent); 46 | 47 | $file->setContent($this->getContentAsString()); 48 | $filesystem->addChild($file); 49 | $reader->setPath($file->url()); 50 | $reader->enableHasHeadline(); 51 | 52 | $this->assertTrue($reader->hasHeadline(), 'has headline'); 53 | $this->assertEquals($expectedContent, $reader->readAll(), 'read all'); 54 | $this->assertEquals($expectedHeadline, $reader->readHeadline(), 'read headline'); 55 | } 56 | 57 | public function testReadWholeContentAtOnce() 58 | { 59 | $file = $this->createFile(); 60 | $filesystem = $this->createFilesystem(); 61 | $reader = $this->createReader(); 62 | 63 | $file->setContent($this->getContentAsString()); 64 | $filesystem->addChild($file); 65 | $reader->setPath($file->url()); 66 | 67 | $this->assertFalse($reader->hasHeadline()); 68 | $this->assertEquals($this->contentAsArray, $reader->readAll()); 69 | } 70 | 71 | public function testReadWholeContentByUsingTheIteratorInterface() 72 | { 73 | $file = $this->createFile(); 74 | $filesystem = $this->createFilesystem(); 75 | $reader = $this->createReader(); 76 | 77 | $file->setContent($this->getContentAsString()); 78 | $filesystem->addChild($file); 79 | $reader->setPath($file->url()); 80 | 81 | $index = 0; 82 | foreach ($reader as $line) { 83 | $this->assertEquals($this->contentAsArray[$index], $line); 84 | ++$index; 85 | } 86 | } 87 | 88 | public function testReadWholeContentByUsingReaderAsAFunction() 89 | { 90 | $file = $this->createFile(); 91 | $filesystem = $this->createFilesystem(); 92 | $reader = $this->createReader(); 93 | 94 | $file->setContent($this->getContentAsString()); 95 | $filesystem->addChild($file); 96 | $reader->setPath($file->url()); 97 | 98 | $index = 0; 99 | 100 | while ($line = $reader()) { 101 | $this->assertEquals($this->contentAsArray[$index], $line); 102 | ++$index; 103 | } 104 | } 105 | 106 | public function testReadWholeContentLinePerLine() 107 | { 108 | $file = $this->createFile(); 109 | $filesystem = $this->createFilesystem(); 110 | $reader = $this->createReader(); 111 | 112 | $file->setContent($this->getContentAsString()); 113 | $filesystem->addChild($file); 114 | $reader->setPath($file->url()); 115 | 116 | $index = 0; 117 | 118 | while ($line = $reader->readOne()) { 119 | $this->assertEquals($this->contentAsArray[$index], $line); 120 | ++$index; 121 | } 122 | } 123 | 124 | /** 125 | * @return array 126 | */ 127 | public function readChunkOfTheContentDataProvider() 128 | { 129 | $content = $this->contentAsArray; 130 | $indices = array_keys($content); 131 | $length = count($indices); 132 | 133 | return [ 134 | 'read only the first line' => [ 135 | 'content' => $content, 136 | 'end' => $indices[1], 137 | 'start' => $indices[0] 138 | ], 139 | 'read one line the middle' => [ 140 | 'content' => $content, 141 | 'end' => $indices[2], 142 | 'start' => $indices[1] 143 | ], 144 | 'read whole content' => [ 145 | 'content' => $content, 146 | 'end' => $indices[($length - 1)], 147 | 'start' => $indices[0] 148 | ] 149 | ]; 150 | } 151 | 152 | /** 153 | * @dataProvider readChunkOfTheContentDataProvider 154 | * @param array $content 155 | * @param int $end 156 | * @param int $start 157 | */ 158 | public function testReadChunkOfTheContentByProvidingStartLineNumberAndAmountOfLines(array $content, $end, $start) 159 | { 160 | $file = $this->createFile(); 161 | $filesystem = $this->createFilesystem(); 162 | $length = ($end - $start); 163 | $reader = $this->createReader(); 164 | 165 | $file->setContent($this->convertArrayToStrings($content)); 166 | $filesystem->addChild($file); 167 | $reader->setPath($file->url()); 168 | 169 | $expectedContent = []; 170 | 171 | $counter = $start; 172 | 173 | while ($counter < $end) { 174 | $expectedContent[] = $content[$counter]; 175 | ++$counter; 176 | } 177 | 178 | $this->assertEquals($expectedContent, $reader->readMany($length, $start)); 179 | } 180 | 181 | public function testReadContentByProvidingTheCurrentLineNumber() 182 | { 183 | $data = $this->contentAsArray; 184 | $file = $this->createFile(); 185 | $filesystem = $this->createFilesystem(); 186 | $reader = $this->createReader(); 187 | 188 | $file->setContent($this->getContentAsString()); 189 | $filesystem->addChild($file); 190 | $reader->setPath($file->url()); 191 | 192 | foreach ($data as $lineNumber => $line) { 193 | $this->assertEquals($line, $reader->readOne($lineNumber)); 194 | } 195 | } 196 | 197 | public function testReadContentByProvidingTheCurrentLineNumberByUsingReaderAsAFunction() 198 | { 199 | $data = $this->contentAsArray; 200 | $file = $this->createFile(); 201 | $filesystem = $this->createFilesystem(); 202 | $reader = $this->createReader(); 203 | $file->setContent($this->getContentAsString()); 204 | $filesystem->addChild($file); 205 | $reader->setPath($file->url()); 206 | foreach($data as $lineNumber => $line) { 207 | $this->assertEquals($line, $reader($lineNumber)); 208 | } 209 | } 210 | 211 | /** 212 | * @return string 213 | */ 214 | protected function getContentAsString() 215 | { 216 | return $this->convertArrayToStrings($this->contentAsArray); 217 | } 218 | 219 | /** 220 | * @param array $data 221 | * @param string $delimiter 222 | * @return string 223 | */ 224 | protected function convertArrayToStrings(array $data, $delimiter = ',') 225 | { 226 | $string = ''; 227 | 228 | foreach ($data as $contents) { 229 | $string .= implode($delimiter, $contents) . PHP_EOL; 230 | } 231 | 232 | return $string; 233 | } 234 | 235 | /** 236 | * @param array $headline 237 | * @param array $content 238 | * @return array 239 | */ 240 | private function addHeadlineAsKeysToContent(array $headline, array $content) 241 | { 242 | $adaptedContent = []; 243 | 244 | foreach ($content as $key => $columns) { 245 | $adaptedContent[$key] = []; 246 | foreach ($columns as $columnKey => $columnContent) { 247 | $adaptedContent[$key][$headline[$columnKey]] = $columnContent; 248 | } 249 | } 250 | 251 | return $adaptedContent; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /test/WriterTest.php: -------------------------------------------------------------------------------- 1 | 4 | * @since 2015-05-03 5 | */ 6 | 7 | namespace Test\Net\Bazzline\Component\Csv; 8 | 9 | //@todo implement call of this tests with different delimiters etc. (after the 10 | //setters are developed 11 | //@todo implement writeOne(!array) 12 | class WriterTest extends AbstractTestCase 13 | { 14 | /** 15 | * @var array 16 | */ 17 | protected $contentAsArray = [ 18 | [ 19 | 'headlines foo', 20 | 'headlines bar' 21 | ], 22 | [ 23 | 'foo', 24 | 'bar' 25 | ], 26 | [ 27 | 'foobar', 28 | 'baz' 29 | ], 30 | [ 31 | 'baz', 32 | 'barfoo' 33 | ] 34 | ]; 35 | 36 | protected $enclosures = [ 37 | 's', 38 | '"', 39 | '|' 40 | ]; 41 | 42 | protected $delimiters = [ 43 | 's', 44 | 'l', 45 | '-', 46 | ',', 47 | ';' 48 | ]; 49 | 50 | public function testWriteContentLinePerLineUsingWriteOne() 51 | { 52 | $delimiters = $this->delimiters; 53 | 54 | foreach ($delimiters as $delimiter) { 55 | $collection = $this->contentAsArray; 56 | $expectedContent = $this->convertArrayToStrings($collection, $delimiter); 57 | $file = $this->createFile(); 58 | $filesystem = $this->createFilesystem(); 59 | $writer = $this->createWriter(); 60 | 61 | $filesystem->addChild($file); 62 | $writer->setDelimiter($delimiter); 63 | $writer->setPath($file->url()); 64 | 65 | foreach ($collection as $content) { 66 | $this->assertNotFalse($writer->writeOne($content)); 67 | } 68 | 69 | $this->assertEquals($expectedContent, $file->getContent()); 70 | } 71 | } 72 | 73 | public function testWriteContentLinePerLineUsingWriterAsAFunction() 74 | { 75 | $delimiters = $this->delimiters; 76 | 77 | foreach ($delimiters as $delimiter) { 78 | $collection = $this->contentAsArray; 79 | $expectedContent = $this->convertArrayToStrings($collection, $delimiter); 80 | $file = $this->createFile(); 81 | $filesystem = $this->createFilesystem(); 82 | $writer = $this->createWriter(); 83 | 84 | $filesystem->addChild($file); 85 | $writer->setDelimiter($delimiter); 86 | $writer->setPath($file->url()); 87 | 88 | foreach ($collection as $content) { 89 | $this->assertNotFalse($writer($content)); 90 | } 91 | 92 | $this->assertEquals($expectedContent, $file->getContent()); 93 | } 94 | } 95 | 96 | public function testWriteAllContentAtOnce() 97 | { 98 | $delimiters = $this->delimiters; 99 | 100 | foreach ($delimiters as $delimiter) { 101 | $collection = $this->contentAsArray; 102 | $expectedContent = $this->convertArrayToStrings($collection, $delimiter); 103 | $file = $this->createFile(); 104 | $filesystem = $this->createFilesystem(); 105 | $writer = $this->createWriter(); 106 | 107 | $filesystem->addChild($file); 108 | $writer->setDelimiter($delimiter); 109 | $writer->setPath($file->url()); 110 | 111 | //simple write the content two times 112 | $this->assertNotFalse($writer->writeMany($collection)); 113 | $this->assertNotFalse($writer->writeAll($collection)); 114 | 115 | $this->assertEquals($expectedContent, $file->getContent()); 116 | } 117 | } 118 | 119 | public function testWriteManyContentAtOnce() 120 | { 121 | $delimiters = $this->delimiters; 122 | 123 | foreach ($delimiters as $delimiter) { 124 | $collection = $this->contentAsArray; 125 | $expectedContent = $this->convertArrayToStrings($collection, $delimiter); 126 | $file = $this->createFile(); 127 | $filesystem = $this->createFilesystem(); 128 | $writer = $this->createWriter(); 129 | 130 | $filesystem->addChild($file); 131 | $writer->setDelimiter($delimiter); 132 | $writer->setPath($file->url()); 133 | 134 | $this->assertNotFalse($writer->writeMany($collection)); 135 | 136 | $this->assertEquals($expectedContent, $file->getContent()); 137 | } 138 | } 139 | 140 | public function testTruncate() 141 | { 142 | $delimiters = $this->delimiters; 143 | 144 | foreach ($delimiters as $delimiter) { 145 | $collection = $this->contentAsArray; 146 | $content = $this->convertArrayToStrings($collection, $delimiter); 147 | $file = $this->createFile(); 148 | $filesystem = $this->createFilesystem(); 149 | $writer = $this->createWriter(); 150 | 151 | $file->setContent($content); 152 | $filesystem->addChild($file); 153 | $writer->setDelimiter($delimiter); 154 | $writer->setPath($file->url()); 155 | 156 | $writer->truncate(); 157 | 158 | $this->assertEquals('', $file->getContent()); 159 | } 160 | } 161 | 162 | public function testDelete() 163 | { 164 | $delimiters = $this->delimiters; 165 | 166 | foreach ($delimiters as $delimiter) { 167 | $collection = $this->contentAsArray; 168 | $content = $this->convertArrayToStrings($collection, $delimiter); 169 | $file = $this->createFile(); 170 | $filesystem = $this->createFilesystem(); 171 | $writer = $this->createWriter(); 172 | 173 | $file->setContent($content); 174 | $filesystem->addChild($file); 175 | $writer->setDelimiter($delimiter); 176 | $writer->setPath($file->url()); 177 | 178 | $this->assertTrue(file_exists($file->url())); 179 | $writer->delete(); 180 | $this->assertFalse(file_exists($file->url())); 181 | } 182 | } 183 | 184 | public function testCopy() 185 | { 186 | $delimiters = $this->delimiters; 187 | 188 | foreach ($delimiters as $delimiter) { 189 | $collection = $this->contentAsArray; 190 | $content = $this->convertArrayToStrings($collection, $delimiter); 191 | $file = $this->createFile(); 192 | $filesystem = $this->createFilesystem(); 193 | $writer = $this->createWriter(); 194 | 195 | $file->setContent($content); 196 | $filesystem->addChild($file); 197 | 198 | $sourceFilePath = $file->url(); 199 | $destinationFilePath = str_replace('test.csv', 'foobar.csv', $file->url()); 200 | 201 | $writer->setDelimiter($delimiter); 202 | $writer->setPath($sourceFilePath); 203 | 204 | $this->assertFalse(file_exists($destinationFilePath)); 205 | $writer->copy($destinationFilePath); 206 | $this->assertTrue(file_exists($destinationFilePath)); 207 | } 208 | } 209 | 210 | public function testCopyWithSettingNewPathAsCurrentPath() 211 | { 212 | $delimiters = $this->delimiters; 213 | 214 | foreach ($delimiters as $delimiter) { 215 | $collection = $this->contentAsArray; 216 | $content = $this->convertArrayToStrings($collection, $delimiter); 217 | $file = $this->createFile(); 218 | $filesystem = $this->createFilesystem(); 219 | $writer = $this->createWriter(); 220 | 221 | $file->setContent($content); 222 | $filesystem->addChild($file); 223 | 224 | $sourceFilePath = $file->url(); 225 | $destinationFilePath = str_replace('test.csv', 'foobar.csv', $file->url()); 226 | 227 | $writer->setDelimiter($delimiter); 228 | $writer->setPath($sourceFilePath); 229 | 230 | $this->assertFalse(file_exists($destinationFilePath)); 231 | $writer->copy($destinationFilePath, true); 232 | $this->assertTrue(file_exists($destinationFilePath)); 233 | } 234 | } 235 | 236 | public function testMove() 237 | { 238 | $delimiters = $this->delimiters; 239 | 240 | foreach ($delimiters as $delimiter) { 241 | $collection = $this->contentAsArray; 242 | $content = $this->convertArrayToStrings($collection, $delimiter); 243 | $file = $this->createFile(); 244 | $filesystem = $this->createFilesystem(); 245 | $writer = $this->createWriter(); 246 | 247 | $file->setContent($content); 248 | $filesystem->addChild($file); 249 | 250 | $sourceFilePath = $file->url(); 251 | $destinationFilePath = str_replace('test.csv', 'foobar.csv', $file->url()); 252 | 253 | $writer->setDelimiter($delimiter); 254 | $writer->setPath($sourceFilePath); 255 | 256 | $this->assertFalse(file_exists($destinationFilePath)); 257 | $writer->move($destinationFilePath); 258 | $this->assertTrue(file_exists($destinationFilePath)); 259 | } 260 | } 261 | 262 | /** 263 | * @param array $data 264 | * @param string $delimiter 265 | * @return string 266 | */ 267 | protected function convertArrayToStrings(array $data, $delimiter = ',') 268 | { 269 | $string = ''; 270 | 271 | foreach ($data as $contents) { 272 | foreach ($contents as &$part) { 273 | $contains = $this->stringContains(' '); 274 | if ($contains->evaluate($part, '', true)) { 275 | $part = '"' . $part . '"'; 276 | } 277 | } 278 | $string .= implode($delimiter, $contents) . PHP_EOL; 279 | } 280 | 281 | return $string; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 |