From ad38a55d79ab4f1944c2ccac82c5f22a7cd4deb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Mon, 26 Jun 2023 14:58:27 +0200 Subject: [PATCH 1/6] Add openspout/openspout bridge as a replacement of box/spout --- .github/labeler.yml | 2 + .github/release.yml | 2 + MAINTAINER.md | 1 + README.md | 3 +- composer.json | 4 + phpstan.neon | 1 + scripts/split-branch | 2 + scripts/split-tag | 2 + src/batch-box-spout/README.md | 7 + .../tests/Writer/FlatFileWriterTest.php | 2 +- src/batch-openspout/.gitattributes | 6 + .../.github/workflows/lockdown.yml | 27 ++ src/batch-openspout/.gitignore | 4 + src/batch-openspout/LICENSE | 19 + src/batch-openspout/README.md | 46 +++ src/batch-openspout/composer.json | 32 ++ .../docs/flat-file-item-reader.md | 38 ++ .../docs/flat-file-item-writer.md | 41 ++ src/batch-openspout/phpunit.xml | 25 ++ .../src/Exception/InvalidRowSizeException.php | 39 ++ .../src/Reader/FlatFileReader.php | 102 +++++ .../src/Reader/HeaderStrategy.php | 99 +++++ .../src/Reader/SheetFilter.php | 72 ++++ .../src/Writer/FlatFileWriter.php | 135 ++++++ .../src/Writer/WriteToSheetItem.php | 49 +++ .../tests/Reader/FlatFileReaderTest.php | 268 ++++++++++++ .../tests/Reader/fixtures/iso-8859-1.csv | 3 + .../tests/Reader/fixtures/multi-tabs.ods | Bin 0 -> 8777 bytes .../tests/Reader/fixtures/multi-tabs.xlsx | Bin 0 -> 6375 bytes .../tests/Reader/fixtures/sample.csv | 4 + .../tests/Reader/fixtures/sample.ods | Bin 0 -> 7842 bytes .../tests/Reader/fixtures/sample.xlsx | Bin 0 -> 4750 bytes .../tests/Reader/fixtures/wrong-line-size.csv | 4 + .../tests/Writer/FlatFileWriterTest.php | 390 ++++++++++++++++++ src/batch-openspout/tests/bootstrap.php | 22 + src/batch/README.md | 3 +- src/batch/docs/domain/item-job/item-reader.md | 4 +- src/batch/docs/domain/item-job/item-writer.md | 4 +- .../ImportDevelopersXlsxToORMTest.php | 10 +- .../integration/Job/SplitDeveloperXlsxJob.php | 14 +- tests/symfony/src/Job/Country/CountryJob.php | 5 +- .../AbstractImportStartWarsEntityJob.php | 8 +- 42 files changed, 1473 insertions(+), 26 deletions(-) create mode 100644 src/batch-openspout/.gitattributes create mode 100644 src/batch-openspout/.github/workflows/lockdown.yml create mode 100644 src/batch-openspout/.gitignore create mode 100644 src/batch-openspout/LICENSE create mode 100644 src/batch-openspout/README.md create mode 100644 src/batch-openspout/composer.json create mode 100644 src/batch-openspout/docs/flat-file-item-reader.md create mode 100644 src/batch-openspout/docs/flat-file-item-writer.md create mode 100644 src/batch-openspout/phpunit.xml create mode 100644 src/batch-openspout/src/Exception/InvalidRowSizeException.php create mode 100644 src/batch-openspout/src/Reader/FlatFileReader.php create mode 100644 src/batch-openspout/src/Reader/HeaderStrategy.php create mode 100644 src/batch-openspout/src/Reader/SheetFilter.php create mode 100644 src/batch-openspout/src/Writer/FlatFileWriter.php create mode 100644 src/batch-openspout/src/Writer/WriteToSheetItem.php create mode 100644 src/batch-openspout/tests/Reader/FlatFileReaderTest.php create mode 100644 src/batch-openspout/tests/Reader/fixtures/iso-8859-1.csv create mode 100644 src/batch-openspout/tests/Reader/fixtures/multi-tabs.ods create mode 100644 src/batch-openspout/tests/Reader/fixtures/multi-tabs.xlsx create mode 100644 src/batch-openspout/tests/Reader/fixtures/sample.csv create mode 100644 src/batch-openspout/tests/Reader/fixtures/sample.ods create mode 100644 src/batch-openspout/tests/Reader/fixtures/sample.xlsx create mode 100644 src/batch-openspout/tests/Reader/fixtures/wrong-line-size.csv create mode 100644 src/batch-openspout/tests/Writer/FlatFileWriterTest.php create mode 100644 src/batch-openspout/tests/bootstrap.php diff --git a/.github/labeler.yml b/.github/labeler.yml index c416b3c1..2d3e1e22 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -11,6 +11,8 @@ - 'src/batch-doctrine-persistence/**' 'yokai/batch-league-flysystem': - 'src/batch-league-flysystem/**' +'yokai/batch-openspout': + - 'src/batch-openspout/**' 'yokai/batch-symfony-console': - 'src/batch-symfony-console/**' 'yokai/batch-symfony-framework': diff --git a/.github/release.yml b/.github/release.yml index 12be75b3..973d1422 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -13,6 +13,8 @@ changelog: labels: ['yokai/batch-doctrine-persistence'] - title: 'Changes made to the `league/flysystem` bridge: `yokai/batch-league-flysystem`' labels: ['yokai/batch-league-flysystem'] + - title: 'Changes made to the `openspout/openspout` bridge: `yokai/batch-openspout`' + labels: ['yokai/batch-openspout'] - title: 'Changes made to the `symfony/console` bridge: `yokai/batch-symfony-console`' labels: ['yokai/batch-symfony-console'] - title: 'Changes made to the `symfony/framework-bundle` bridge: `yokai/batch-symfony-framework`' diff --git a/MAINTAINER.md b/MAINTAINER.md index 8e8cb8d9..e0d40f1f 100644 --- a/MAINTAINER.md +++ b/MAINTAINER.md @@ -36,6 +36,7 @@ see https://github.com/yokai-php/batch-src/releases/tag/{created tag} - https://github.com/yokai-php/batch-doctrine-orm/releases/new - https://github.com/yokai-php/batch-doctrine-persistence/releases/new - https://github.com/yokai-php/batch-league-flysystem/releases/new + - https://github.com/yokai-php/batch-openspout/releases/new - https://github.com/yokai-php/batch-symfony-console/releases/new - https://github.com/yokai-php/batch-symfony-framework/releases/new - https://github.com/yokai-php/batch-symfony-messenger/releases/new diff --git a/README.md b/README.md index d1445324..7b9009c9 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ Some bridges to popular packages : | Bridge with | | |------------------------------------------------------------------------------------|----------------------------------------------------------------| -| [`box/spout`](https://github.com/yokai-php/batch-box-spout) | Read/Write from/to CSV/ODS/XLSX | +| `DEPRECATED` [`box/spout`](https://github.com/yokai-php/batch-box-spout) | Read/Write from/to CSV/ODS/XLSX | | [`doctrine/dbal`](https://github.com/yokai-php/batch-doctrine-dbal) | Read/Write from/to SQL databases | | [`doctrine/orm`](https://github.com/yokai-php/batch-doctrine-orm) | Read from Doctrine ORM entities | | [`doctrine/persistence`](https://github.com/yokai-php/batch-doctrine-persistence) | Write to Doctrine ORM/ODM objects | | [`league/flysystem`](https://github.com/yokai-php/batch-league-flysystem) | Copy/Move files in a job / Trigger job when file found | +| [`openspout/openspout`](https://github.com/yokai-php/batch-openspout) | Read/Write from/to CSV/ODS/XLSX | | [`symfony/console`](https://github.com/yokai-php/batch-symfony-console) | Add command to trigger jobs and async job launcher via command | | [`symfony/framework-bundle`](https://github.com/yokai-php/batch-symfony-framework) | Bundle to integrate with Symfony framework | | [`symfony/messenger`](https://github.com/yokai-php/batch-symfony-messenger) | Trigger jobs using message dispatch | diff --git a/composer.json b/composer.json index c168bf36..45984ece 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "doctrine/orm": "^2.8", "doctrine/persistence": "^2.0|^3.0", "league/flysystem": "^3.0", + "openspout/openspout": "^4.0", "psr/container": "^1.0", "psr/event-dispatcher": "^1.0", "psr/log": "^1.0|^2.0|^3.0", @@ -50,6 +51,7 @@ "yokai/batch-doctrine-orm": "self.version", "yokai/batch-doctrine-persistence": "self.version", "yokai/batch-league-flysystem": "self.version", + "yokai/batch-openspout": "self.version", "yokai/batch-symfony-console": "self.version", "yokai/batch-symfony-framework": "self.version", "yokai/batch-symfony-messenger": "self.version", @@ -64,6 +66,7 @@ "Yokai\\Batch\\Bridge\\Doctrine\\ORM\\": "src/batch-doctrine-orm/src/", "Yokai\\Batch\\Bridge\\Doctrine\\Persistence\\": "src/batch-doctrine-persistence/src/", "Yokai\\Batch\\Bridge\\League\\Flysystem\\": "src/batch-league-flysystem/src/", + "Yokai\\Batch\\Bridge\\OpenSpout\\": "src/batch-openspout/src/", "Yokai\\Batch\\Bridge\\Symfony\\Console\\": "src/batch-symfony-console/src/", "Yokai\\Batch\\Bridge\\Symfony\\Framework\\": "src/batch-symfony-framework/src/", "Yokai\\Batch\\Bridge\\Symfony\\Messenger\\": "src/batch-symfony-messenger/src/", @@ -83,6 +86,7 @@ "Yokai\\Batch\\Tests\\Bridge\\Doctrine\\ORM\\": "src/batch-doctrine-orm/tests/", "Yokai\\Batch\\Tests\\Bridge\\Doctrine\\Persistence\\": "src/batch-doctrine-persistence/tests/", "Yokai\\Batch\\Tests\\Bridge\\League\\Flysystem\\": "src/batch-league-flysystem/tests/", + "Yokai\\Batch\\Tests\\Bridge\\OpenSpout\\": "src/batch-openspout/tests/", "Yokai\\Batch\\Tests\\Bridge\\Symfony\\Console\\": "src/batch-symfony-console/tests/", "Yokai\\Batch\\Tests\\Bridge\\Symfony\\Framework\\": "src/batch-symfony-framework/tests/", "Yokai\\Batch\\Tests\\Bridge\\Symfony\\Messenger\\": "src/batch-symfony-messenger/tests/", diff --git a/phpstan.neon b/phpstan.neon index c035bb0b..898616e1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,6 +10,7 @@ parameters: - src/batch-doctrine-orm/src/ - src/batch-doctrine-persistence/src/ - src/batch-league-flysystem/src/ + - src/batch-openspout/src/ - src/batch-symfony-console/src/ - src/batch-symfony-framework/src/ - src/batch-symfony-messenger/src/ diff --git a/scripts/split-branch b/scripts/split-branch index ba491e54..1fb450d7 100755 --- a/scripts/split-branch +++ b/scripts/split-branch @@ -30,6 +30,7 @@ remote batch-doctrine-dbal git@github.com:yokai-php/batch-doctrine-dbal.g remote batch-doctrine-orm git@github.com:yokai-php/batch-doctrine-orm.git remote batch-doctrine-persistence git@github.com:yokai-php/batch-doctrine-persistence.git remote batch-league-flysystem git@github.com:yokai-php/batch-league-flysystem.git +remote batch-openspout git@github.com:yokai-php/batch-openspout.git remote batch-symfony-console git@github.com:yokai-php/batch-symfony-console.git remote batch-symfony-framework git@github.com:yokai-php/batch-symfony-framework.git remote batch-symfony-messenger git@github.com:yokai-php/batch-symfony-messenger.git @@ -43,6 +44,7 @@ split 'src/batch-doctrine-dbal' batch-doctrine-dbal split 'src/batch-doctrine-orm' batch-doctrine-orm split 'src/batch-doctrine-persistence' batch-doctrine-persistence split 'src/batch-league-flysystem' batch-league-flysystem +split 'src/batch-openspout' batch-openspout split 'src/batch-symfony-console' batch-symfony-console split 'src/batch-symfony-framework' batch-symfony-framework split 'src/batch-symfony-messenger' batch-symfony-messenger diff --git a/scripts/split-tag b/scripts/split-tag index deb87215..fb3a2659 100755 --- a/scripts/split-tag +++ b/scripts/split-tag @@ -30,6 +30,7 @@ remote batch-doctrine-dbal git@github.com:yokai-php/batch-doctrine-dbal.g remote batch-doctrine-orm git@github.com:yokai-php/batch-doctrine-orm.git remote batch-doctrine-persistence git@github.com:yokai-php/batch-doctrine-persistence.git remote batch-league-flysystem git@github.com:yokai-php/batch-league-flysystem.git +remote batch-openspout git@github.com:yokai-php/batch-openspout.git remote batch-symfony-console git@github.com:yokai-php/batch-symfony-console.git remote batch-symfony-framework git@github.com:yokai-php/batch-symfony-framework.git remote batch-symfony-messenger git@github.com:yokai-php/batch-symfony-messenger.git @@ -43,6 +44,7 @@ split 'src/batch-doctrine-dbal' batch-doctrine-dbal split 'src/batch-doctrine-orm' batch-doctrine-orm split 'src/batch-doctrine-persistence' batch-doctrine-persistence split 'src/batch-league-flysystem' batch-league-flysystem +split 'src/batch-openspout' batch-openspout split 'src/batch-symfony-console' batch-symfony-console split 'src/batch-symfony-framework' batch-symfony-framework split 'src/batch-symfony-messenger' batch-symfony-messenger diff --git a/src/batch-box-spout/README.md b/src/batch-box-spout/README.md index 42d45f7f..839d11b6 100644 --- a/src/batch-box-spout/README.md +++ b/src/batch-box-spout/README.md @@ -6,6 +6,13 @@ [`box/spout`](https://github.com/box/spout) bridge for [Batch](https://github.com/yokai-php/batch) processing library. +## :warning: DEPRECATED + +This library is deprecated because the package it relies on was also deprecated. +The library has been replaced with [`openspout/openspout`](https://github.com/openspout/openspout). +And this bridge was replaced with [`yokai/batch-openspout`](https://github.com/yokai-php/batch-openspout). + + ## :warning: BETA This library is following [semver](https://semver.org/). diff --git a/src/batch-box-spout/tests/Writer/FlatFileWriterTest.php b/src/batch-box-spout/tests/Writer/FlatFileWriterTest.php index ce7fb0bd..0ad596e6 100644 --- a/src/batch-box-spout/tests/Writer/FlatFileWriterTest.php +++ b/src/batch-box-spout/tests/Writer/FlatFileWriterTest.php @@ -22,7 +22,7 @@ class FlatFileWriterTest extends TestCase { - private const WRITE_DIR = ARTIFACT_DIR . '/flat-file-writer'; + private const WRITE_DIR = ARTIFACT_DIR . '/box-spout-flat-file-writer'; /** * @dataProvider sets diff --git a/src/batch-openspout/.gitattributes b/src/batch-openspout/.gitattributes new file mode 100644 index 00000000..62fd109e --- /dev/null +++ b/src/batch-openspout/.gitattributes @@ -0,0 +1,6 @@ +.gitattributes export-ignore +.gitignore export-ignore +docs/ export-ignore +tests/ export-ignore +LICENSE export-ignore +*.md export-ignore diff --git a/src/batch-openspout/.github/workflows/lockdown.yml b/src/batch-openspout/.github/workflows/lockdown.yml new file mode 100644 index 00000000..aee0811b --- /dev/null +++ b/src/batch-openspout/.github/workflows/lockdown.yml @@ -0,0 +1,27 @@ +name: 'Lock down Pull Requests' + +on: + pull_request: + types: opened + +jobs: + lockdown: + runs-on: ubuntu-latest + steps: + - uses: dessant/repo-lockdown@v2 + with: + github-token: ${{ github.token }} + close-pr: true + lock-pr: true + pr-comment: > + Thanks for your pull request! + + However, this repository does not accept pull requests, + see the README for details. + + If you want to contribute, + you should instead open a pull request on the main repository: + + https://github.com/yokai-php/batch-src + + Thank you diff --git a/src/batch-openspout/.gitignore b/src/batch-openspout/.gitignore new file mode 100644 index 00000000..88259615 --- /dev/null +++ b/src/batch-openspout/.gitignore @@ -0,0 +1,4 @@ +/.phpunit.result.cache +/tests/.artifacts/ +/vendor/ +/composer.lock diff --git a/src/batch-openspout/LICENSE b/src/batch-openspout/LICENSE new file mode 100644 index 00000000..83096655 --- /dev/null +++ b/src/batch-openspout/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019 Yann Eugoné + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/batch-openspout/README.md b/src/batch-openspout/README.md new file mode 100644 index 00000000..2933f758 --- /dev/null +++ b/src/batch-openspout/README.md @@ -0,0 +1,46 @@ +# box/spout bridge for Batch processing library + +[![Latest Stable Version](https://img.shields.io/packagist/v/yokai/batch-openspout?style=flat-square)](https://packagist.org/packages/yokai/batch-openspout) +[![Downloads Monthly](https://img.shields.io/packagist/dm/yokai/batch-openspout?style=flat-square)](https://packagist.org/packages/yokai/batch-openspout) + +[`openspout/openspout`](https://github.com/openspout/openspout) bridge for [Batch](https://github.com/yokai-php/batch) processing library. + + +## :warning: BETA + +This library is following [semver](https://semver.org/). +However before we reach the first stable version (`v1.0.0`), we may decide to introduce **API changes in minor versions**. +This is why you should stick to a `v0.[minor].*` requirement ! + + +# Installation + +``` +composer require yokai/batch-openspout +``` + + +## Documentation + +This package provides: + +- a [item reader](docs/flat-file-item-reader.md) that read from CSV/XLSX/ODS files +- a [item writer](docs/flat-file-item-writer.md) that write to CSV/XLSX/ODS files + + +## Contribution + +This package is a readonly split of a [larger repository](https://github.com/yokai-php/batch-src), +containing all tests and sources for all librairies of the batch universe. + +Please feel free to open an [issue](https://github.com/yokai-php/batch-src/issues) +or a [pull request](https://github.com/yokai-php/batch-src/pulls) +in the [main repository](https://github.com/yokai-php/batch-src). + +The library was originally created by [Yann Eugoné](https://github.com/yann-eugone). +See the list of [contributors](https://github.com/yokai-php/batch-src/contributors). + + +## License + +This library is under MIT [LICENSE](LICENSE). diff --git a/src/batch-openspout/composer.json b/src/batch-openspout/composer.json new file mode 100644 index 00000000..294860ff --- /dev/null +++ b/src/batch-openspout/composer.json @@ -0,0 +1,32 @@ +{ + "name": "yokai/batch-openspout", + "description": "openspout/openspout bridge for yokai/batch", + "keywords": ["batch", "job", "reader", "writer", "flat", "csv", "xlsx", "ods"], + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Yann Eugoné", + "email": "eugone.yann@gmail.com" + } + ], + "require": { + "php": "^8.0", + "openspout/openspout": "^4.0", + "yokai/batch": "^0.5.0" + }, + "autoload": { + "psr-4": { + "Yokai\\Batch\\Bridge\\OpenSpout\\": "src/" + } + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/filesystem": "^5.0|^6.0" + }, + "autoload-dev": { + "psr-4": { + "Yokai\\Batch\\Tests\\Bridge\\OpenSpout\\": "tests/" + } + } +} diff --git a/src/batch-openspout/docs/flat-file-item-reader.md b/src/batch-openspout/docs/flat-file-item-reader.md new file mode 100644 index 00000000..5af12266 --- /dev/null +++ b/src/batch-openspout/docs/flat-file-item-reader.md @@ -0,0 +1,38 @@ +# Item reader with CSV/ODS/XLSX files + +The [FlatFileReader](../src/Reader/FlatFileReader.php) is a reader +that will read from CSV/ODS/XLSX file and return each line as an array. + +```php +setFontBold()->build()), + ['static', 'header', 'keys'] +); +``` + +## On the same subject + +- [What is an item writer ?](https://github.com/yokai-php/batch/blob/0.x/docs/domain/item-job/item-writer.md) diff --git a/src/batch-openspout/phpunit.xml b/src/batch-openspout/phpunit.xml new file mode 100644 index 00000000..f2bbace6 --- /dev/null +++ b/src/batch-openspout/phpunit.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + ./tests + + + + + + ./src + + + diff --git a/src/batch-openspout/src/Exception/InvalidRowSizeException.php b/src/batch-openspout/src/Exception/InvalidRowSizeException.php new file mode 100644 index 00000000..ed952cdd --- /dev/null +++ b/src/batch-openspout/src/Exception/InvalidRowSizeException.php @@ -0,0 +1,39 @@ + + */ + private array $headers, + /** + * @var array + */ + private array $row, + ) { + parent::__construct('Invalid row size'); + } + + /** + * @return array + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * @return array + */ + public function getRow(): array + { + return $this->row; + } +} diff --git a/src/batch-openspout/src/Reader/FlatFileReader.php b/src/batch-openspout/src/Reader/FlatFileReader.php new file mode 100644 index 00000000..50853e8b --- /dev/null +++ b/src/batch-openspout/src/Reader/FlatFileReader.php @@ -0,0 +1,102 @@ +sheetFilter = $sheetFilter ?? SheetFilter::all(); + $this->headerStrategy = $headerStrategy ?? HeaderStrategy::none(); + } + + public function read(): iterable + { + /** @var string $path */ + $path = $this->filePath->get($this->jobExecution); + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + $reader = match ($extension) { + 'csv' => new CSVReader($this->options), + 'xlsx' => new XLSXReader($this->options), + 'ods' => new ODSReader($this->options), + default => throw new UnsupportedTypeException('No readers supporting the given type: '.$extension), + }; + + $reader->open($path); + + foreach ($this->rows($reader) as $rowIndex => $row) { + if ($rowIndex === 1) { + if (!$this->headerStrategy->setHeaders($row)) { + continue; + } + } + + try { + yield $this->headerStrategy->getItem($row); + } catch (InvalidRowSizeException $exception) { + $this->jobExecution->addWarning( + new Warning( + sprintf( + 'Expecting row %s to have exactly %d columns(s), but got %d.', + $rowIndex, + count($exception->getHeaders()), + count($exception->getRow()), + ), + [], + ['headers' => $exception->getHeaders(), 'row' => $exception->getRow()] + ) + ); + } + } + + $reader->close(); + } + + /** + * @return Generator> + */ + private function rows(ReaderInterface $reader): Generator + { + foreach ($this->sheetFilter->list($reader) as $sheet) { + /** @var int $rowIndex */ + /** @var Row $row */ + foreach ($sheet->getRowIterator() as $rowIndex => $row) { + yield $rowIndex => $row->toArray(); + } + } + } +} diff --git a/src/batch-openspout/src/Reader/HeaderStrategy.php b/src/batch-openspout/src/Reader/HeaderStrategy.php new file mode 100644 index 00000000..df164f6d --- /dev/null +++ b/src/batch-openspout/src/Reader/HeaderStrategy.php @@ -0,0 +1,99 @@ +|null + */ + private ?array $headers + ) { + } + + /** + * Read file has headers but should be skipped. + * + * @param list|null $headers + */ + public static function skip(array $headers = null): self + { + return new self(self::SKIP, $headers); + } + + /** + * Read file has headers and should be used to array_combine each row. + */ + public static function combine(): self + { + return new self(self::COMBINE, null); + } + + /** + * Read file has no headers. + * + * @param list|null $headers + */ + public static function none(array $headers = null): self + { + return new self(self::NONE, $headers); + } + + /** + * @param list $headers + * @internal + */ + public function setHeaders(array $headers): bool + { + if ($this->mode === self::NONE) { + return true; // row should be read, will be considered as an item + } + if ($this->mode === self::COMBINE) { + $this->headers = $headers; + } + + return false; // row should be skipped, will not be considered as an item + } + + /** + * Build the associative item, a combination of headers and values. + * + * @throws InvalidRowSizeException + * + * @param array $row + * + * @return array + * @internal + */ + public function getItem(array $row): array + { + if ($this->headers === null) { + return $row; // headers were not set, read row as is + } + + try { + /** @var array $combined */ + $combined = @array_combine($this->headers, $row); + } catch (\ValueError) { + throw new InvalidRowSizeException($this->headers, $row); + } + + return $combined; + } +} diff --git a/src/batch-openspout/src/Reader/SheetFilter.php b/src/batch-openspout/src/Reader/SheetFilter.php new file mode 100644 index 00000000..331cb206 --- /dev/null +++ b/src/batch-openspout/src/Reader/SheetFilter.php @@ -0,0 +1,72 @@ +accept = $accept; + } + + /** + * Will read every sheets in file. + */ + public static function all(): self + { + return new self(fn() => true); + } + + /** + * Will read sheets that are at specified indexes. + */ + public static function indexIs(int $index, int ...$indexes): self + { + $indexes[] = $index; + + return new self(fn(SheetInterface $sheet) => \in_array($sheet->getIndex(), $indexes, true)); + } + + /** + * Will read sheets that are named as specified. + */ + public static function nameIs(string $name, string ...$names): self + { + $names[] = $name; + + return new self(fn(SheetInterface $sheet) => \in_array($sheet->getName(), $names, true)); + } + + /** + * Iterate over valid sheets for the provided filter. + * + * @return Generator + * @internal + */ + public function list(ReaderInterface $reader): Generator + { + /** @var SheetInterface $sheet */ + foreach ($reader->getSheetIterator() as $sheet) { + if (($this->accept)($sheet)) { + yield $sheet; + } + } + } +} diff --git a/src/batch-openspout/src/Writer/FlatFileWriter.php b/src/batch-openspout/src/Writer/FlatFileWriter.php new file mode 100644 index 00000000..26abb836 --- /dev/null +++ b/src/batch-openspout/src/Writer/FlatFileWriter.php @@ -0,0 +1,135 @@ +|null + */ + private ?array $headers = null, + ) { + } + + public function initialize(): void + { + /** @var string $path */ + $path = $this->filePath->get($this->jobExecution); + $dir = \dirname($path); + if (!@\is_dir($dir) && !@\mkdir($dir, 0777, true)) { + throw new RuntimeException(\sprintf('Cannot create dir "%s".', $dir)); + } + + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + $this->writer = match ($extension) { + 'csv' => new CSVWriter($this->options), + 'xlsx' => new XLSXWriter($this->options), + 'ods' => new ODSWriter($this->options), + default => throw new UnsupportedTypeException('No writers supporting the given type: ' . $extension), + }; + $this->writer->openToFile($path); + + if ($this->writer instanceof AbstractWriterMultiSheets) { + $this->defaultSheet = $this->writer->getCurrentSheet()->getName(); + } + } + + public function write(iterable $items): void + { + $writer = $this->writer; + if ($writer === null) { + throw BadMethodCallException::itemComponentNotInitialized($this); + } + + if (!$this->headersAdded) { + $this->headersAdded = true; + if ($this->headers !== null) { + $writer->addRow(Row::fromValues($this->headers)); + } + } + + foreach ($items as $row) { + if ($row instanceof WriteToSheetItem) { + $this->changeSheet($row->getSheet()); + $row = $row->getItem(); + } elseif ($this->defaultSheet !== null) { + $this->changeSheet($this->defaultSheet); + } + if (\is_array($row)) { + $row = Row::fromValues($row); + } + if (!$row instanceof Row) { + throw UnexpectedValueException::type('array|' . Row::class, $row); + } + + $writer->addRow($row); + } + } + + public function flush(): void + { + if ($this->writer === null) { + throw BadMethodCallException::itemComponentNotInitialized($this); + } + + $this->writer->close(); + $this->writer = null; + $this->headersAdded = false; + } + + private function changeSheet(string $name): void + { + if (!$this->writer instanceof AbstractWriterMultiSheets) { + return; + } + + foreach ($this->writer->getSheets() as $sheet) { + if ($sheet->getName() === $name) { + $this->writer->setCurrentSheet($sheet); + return; + } + } + + $sheet = $this->writer->addNewSheetAndMakeItCurrent(); + $sheet->setName($name); + } +} diff --git a/src/batch-openspout/src/Writer/WriteToSheetItem.php b/src/batch-openspout/src/Writer/WriteToSheetItem.php new file mode 100644 index 00000000..466558b7 --- /dev/null +++ b/src/batch-openspout/src/Writer/WriteToSheetItem.php @@ -0,0 +1,49 @@ + $item + */ + public static function array(string $sheet, array $item, Style $style = null): self + { + return new self($sheet, Row::fromValues($item, $style)); + } + + /** + * Static constructor from {@see Row} object. + */ + public static function row(string $sheet, Row $item): self + { + return new self($sheet, $item); + } + + public function getSheet(): string + { + return $this->sheet; + } + + public function getItem(): Row + { + return $this->item; + } +} diff --git a/src/batch-openspout/tests/Reader/FlatFileReaderTest.php b/src/batch-openspout/tests/Reader/FlatFileReaderTest.php new file mode 100644 index 00000000..614adf09 --- /dev/null +++ b/src/batch-openspout/tests/Reader/FlatFileReaderTest.php @@ -0,0 +1,268 @@ +setJobExecution($jobExecution); + + /** @var \Iterator $got */ + $got = $reader->read(); + self::assertInstanceOf(\Iterator::class, $got); + self::assertSame($expected, iterator_to_array($got)); + } + + public function sets(): Generator + { + $csv = __DIR__ . '/fixtures/sample.csv'; + $ods = __DIR__ . '/fixtures/sample.ods'; + $xlsx = __DIR__ . '/fixtures/sample.xlsx'; + + // first line is not header + $expected = [ + ['firstName', 'lastName'], + ['John', 'Doe'], + ['Jane', 'Doe'], + ['Jack', 'Doe'], + ]; + foreach ([$csv, $ods, $xlsx] as $file) { + yield [ + $file, + null, + null, + fn() => HeaderStrategy::none(), + $expected, + ]; + } + + // first line is header and should be skipped + $expected = [ + ['John', 'Doe'], + ['Jane', 'Doe'], + ['Jack', 'Doe'], + ]; + foreach ([$csv, $ods, $xlsx] as $file) { + yield [ + $file, + null, + null, + fn() => HeaderStrategy::skip(), + $expected, + ]; + } + + // first line is header and should be skipped, but headers is provided with static value + $expected = [ + ['prenom' => 'John', 'nom' => 'Doe'], + ['prenom' => 'Jane', 'nom' => 'Doe'], + ['prenom' => 'Jack', 'nom' => 'Doe'], + ]; + foreach ([$csv, $ods, $xlsx] as $file) { + yield [ + $file, + null, + null, + fn() => HeaderStrategy::skip(['prenom', 'nom']), + $expected, + ]; + } + + // first line is header and should be skipped + $expected = [ + ['firstName' => 'John', 'lastName' => 'Doe'], + ['firstName' => 'Jane', 'lastName' => 'Doe'], + ['firstName' => 'Jack', 'lastName' => 'Doe'], + ]; + foreach ([$csv, $ods, $xlsx] as $file) { + yield [ + $file, + null, + null, + fn() => HeaderStrategy::combine(), + $expected, + ]; + } + + // non-standard CSV (delimiter and enclosure changed) encoded in ISO-8859 + $options = new CSVOptions(); + $options->FIELD_DELIMITER = ';'; + $options->FIELD_ENCLOSURE = '|'; + $options->ENCODING = 'ISO-8859-1'; + yield [ + __DIR__ . '/fixtures/iso-8859-1.csv', + fn() => $options, + null, + null, + [ + ['Gérard', 'À peu près'], + ['Benoît', 'Bien-être'], + ['Gaëlle', 'Ça va'], + ], + ]; + + // change files to multi tab + $ods = __DIR__ . '/fixtures/multi-tabs.ods'; + $xlsx = __DIR__ . '/fixtures/multi-tabs.xlsx'; + + // multi-tab files, 1st tab + $expected = [ + ['firstName' => 'John', 'lastName' => 'Doe'], + ['firstName' => 'Jane', 'lastName' => 'Doe'], + ['firstName' => 'Jack', 'lastName' => 'Doe'], + ]; + foreach ([$ods, $xlsx] as $file) { + yield [ + $file, + null, + fn() => SheetFilter::indexIs(0), + fn() => HeaderStrategy::combine(), + $expected, + ]; + } + + // multi-tab files, tab "Français" + $expected = [ + ['prénom' => 'Jean', 'nom' => 'Bon'], + ['prénom' => 'Jeanne', 'nom' => 'Aimar'], + ['prénom' => 'Jacques', 'nom' => 'Ouzi'], + ]; + foreach ([$ods, $xlsx] as $file) { + yield [ + $file, + null, + fn() => SheetFilter::nameIs('Français'), + fn() => HeaderStrategy::combine(), + $expected, + ]; + } + + // multi-tab files, all tabs + $expected = [ + ['firstName' => 'John', 'lastName' => 'Doe'], + ['firstName' => 'Jane', 'lastName' => 'Doe'], + ['firstName' => 'Jack', 'lastName' => 'Doe'], + ['prénom' => 'Jean', 'nom' => 'Bon'], + ['prénom' => 'Jeanne', 'nom' => 'Aimar'], + ['prénom' => 'Jacques', 'nom' => 'Ouzi'], + ]; + foreach ([$ods, $xlsx] as $file) { + yield [ + $file, + null, + fn() => SheetFilter::all(), + fn() => HeaderStrategy::combine(), + $expected, + ]; + } + } + + public function testReadWrongLineSize(): void + { + $file = __DIR__ . '/fixtures/wrong-line-size.csv'; + $jobExecution = JobExecution::createRoot('123456789', 'parent'); + $reader = new FlatFileReader( + new StaticValueParameterAccessor($file), + null, + null, + HeaderStrategy::combine(), + ); + $reader->setJobExecution($jobExecution); + + /** @var \Iterator $result */ + $result = $reader->read(); + self::assertInstanceOf(\Iterator::class, $result); + self::assertSame( + [ + ['firstName' => 'John', 'lastName' => 'Doe'], + ['firstName' => 'Jack', 'lastName' => 'Doe'], + ], + iterator_to_array($result) + ); + + self::assertSame( + 'Expecting row 3 to have exactly 2 columns(s), but got 3.', + $jobExecution->getWarnings()[0]->getMessage() + ); + self::assertSame( + ['headers' => ['firstName', 'lastName'], 'row' => ['Jane', 'Doe', 'too much data']], + $jobExecution->getWarnings()[0]->getContext() + ); + } + + /** + * @dataProvider wrongOptions + */ + public function testWrongOptions(string $file, callable $options): void + { + $this->expectException(\TypeError::class); + + $jobExecution = JobExecution::createRoot('123456789', 'parent'); + $reader = new FlatFileReader(new StaticValueParameterAccessor($file), $options()); + $reader->setJobExecution($jobExecution); + + iterator_to_array($reader->read()); + } + + public function wrongOptions(): \Generator + { + // with CSV file, CSVOptions is expected + yield [ + __DIR__ . '/fixtures/sample.csv', + fn() => new XLSXOptions(), + ]; + yield [ + __DIR__ . '/fixtures/sample.csv', + fn() => new ODSOptions(), + ]; + + // with ODS file, ODSOptions is expected + yield [ + __DIR__ . '/fixtures/sample.ods', + fn() => new CSVOptions(), + ]; + yield [ + __DIR__ . '/fixtures/sample.ods', + fn() => new XLSXOptions(), + ]; + + // with XLSX file, XLSXOptions is expected + yield [ + __DIR__ . '/fixtures/sample.xlsx', + fn() => new CSVOptions(), + ]; + yield [ + __DIR__ . '/fixtures/sample.xlsx', + fn() => new ODSOptions(), + ]; + } +} diff --git a/src/batch-openspout/tests/Reader/fixtures/iso-8859-1.csv b/src/batch-openspout/tests/Reader/fixtures/iso-8859-1.csv new file mode 100644 index 00000000..df0df07c --- /dev/null +++ b/src/batch-openspout/tests/Reader/fixtures/iso-8859-1.csv @@ -0,0 +1,3 @@ +|Gérard|;|À peu près| +|Benoît|;|Bien-être| +|Gaëlle|;|Ça va| diff --git a/src/batch-openspout/tests/Reader/fixtures/multi-tabs.ods b/src/batch-openspout/tests/Reader/fixtures/multi-tabs.ods new file mode 100644 index 0000000000000000000000000000000000000000..1ba774d4c9816248fb22ef32c0ea67547220c4d5 GIT binary patch literal 8777 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 RG&BO#)rTrb1e8~I{{nV(yH5ZB literal 0 HcmV?d00001 diff --git a/src/batch-openspout/tests/Reader/fixtures/sample.csv b/src/batch-openspout/tests/Reader/fixtures/sample.csv new file mode 100644 index 00000000..08eff973 --- /dev/null +++ b/src/batch-openspout/tests/Reader/fixtures/sample.csv @@ -0,0 +1,4 @@ +firstName,lastName +John,Doe +Jane,Doe +Jack,Doe diff --git a/src/batch-openspout/tests/Reader/fixtures/sample.ods b/src/batch-openspout/tests/Reader/fixtures/sample.ods new file mode 100644 index 0000000000000000000000000000000000000000..80ae1907a549a99b3ec730607d29d2059df2528d GIT binary patch literal 7842 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 HeB}QCAl}(W literal 0 HcmV?d00001 diff --git a/src/batch-openspout/tests/Reader/fixtures/wrong-line-size.csv b/src/batch-openspout/tests/Reader/fixtures/wrong-line-size.csv new file mode 100644 index 00000000..c5985ddc --- /dev/null +++ b/src/batch-openspout/tests/Reader/fixtures/wrong-line-size.csv @@ -0,0 +1,4 @@ +firstName,lastName +John,Doe +Jane,Doe,too much data +Jack,Doe diff --git a/src/batch-openspout/tests/Writer/FlatFileWriterTest.php b/src/batch-openspout/tests/Writer/FlatFileWriterTest.php new file mode 100644 index 00000000..c8e321e0 --- /dev/null +++ b/src/batch-openspout/tests/Writer/FlatFileWriterTest.php @@ -0,0 +1,390 @@ +setJobExecution(JobExecution::createRoot('123456789', 'export')); + + $writer->initialize(); + $writer->write($itemsToWrite); + $writer->flush(); + + self::assertFileContents($file, $expectedContent); + } + + public function sets(): \Generator + { + $headers = ['firstName', 'lastName']; + $items = [ + ['John', 'Doe'], + ['Jane', 'Doe'], + ['Jack', 'Doe'], + ]; + $contentWithoutHeader = <<types() as [$type]) { + yield [ + "no-header.$type", + null, + null, + $items, + $contentWithoutHeader, + ]; + yield [ + "with-header.$type", + null, + $headers, + $items, + $contentWithHeader, + ]; + } + + $options = new CSVOptions(); + $options->FIELD_DELIMITER = ';'; + $options->FIELD_ENCLOSURE = '|'; + $content = << $options, + null, + $items, + $content, + ]; + + $style = (new Style()) + ->setFontBold() + ->setFontSize(15) + ->setFontColor(Color::BLUE) + ->setShouldWrapText() + ->setCellAlignment(CellAlignment::RIGHT) + ->setBackgroundColor(Color::YELLOW); + + $options = new XLSXOptions(); + $options->DEFAULT_ROW_STYLE = $style; + yield [ + "total-style.xlsx", + fn() => $options, + null, + $items, + $contentWithoutHeader, + ]; + $options = new ODSOptions(); + $options->DEFAULT_ROW_STYLE = $style; + yield [ + "total-style.ods", + fn() => $options, + null, + $items, + $contentWithoutHeader, + ]; + + $blue = (new Style()) + ->setFontBold() + ->setFontColor(Color::BLUE); + $red = (new Style()) + ->setFontBold() + ->setFontColor(Color::RED); + $green = (new Style()) + ->setFontBold() + ->setFontColor(Color::GREEN); + $styledItems = [ + Row::fromValues(['John', 'Doe'], $blue), + Row::fromValues(['Jane', 'Doe'], $red), + Row::fromValues(['Jack', 'Doe'], $green), + ]; + yield [ + "partial-style.xlsx", + null, + null, + $styledItems, + $contentWithoutHeader, + ]; + yield [ + "partial-style.ods", + null, + null, + $styledItems, + $contentWithoutHeader, + ]; + } + + /** + * @dataProvider types + */ + public function testWriteInvalidItem(string $type): void + { + $this->expectException(UnexpectedValueException::class); + + $file = self::WRITE_DIR . '/invalid-item.' . $type; + $writer = new FlatFileWriter(new StaticValueParameterAccessor($file)); + $writer->setJobExecution(JobExecution::createRoot('123456789', 'export')); + + $writer->initialize(); + $writer->write([true]); // writer accept collection of array or \OpenSpout\Common\Entity\Row + } + + /** + * @dataProvider types + */ + public function testCannotCreateFile(string $type): void + { + $this->expectException(RuntimeException::class); + + $file = '/path/to/a/dir/that/do/not/exists/and/not/creatable/file.' . $type; + $writer = new FlatFileWriter(new StaticValueParameterAccessor($file)); + $writer->setJobExecution(JobExecution::createRoot('123456789', 'export')); + + $writer->initialize(); + } + + /** + * @dataProvider types + */ + public function testShouldInitializeBeforeWrite(string $type): void + { + $this->expectException(BadMethodCallException::class); + + $file = self::WRITE_DIR . '/should-initialize-before-write.' . $type; + $writer = new FlatFileWriter(new StaticValueParameterAccessor($file)); + $writer->write([true]); + } + + /** + * @dataProvider types + */ + public function testShouldInitializeBeforeFlush(string $type): void + { + $this->expectException(BadMethodCallException::class); + + $file = self::WRITE_DIR . '/should-initialize-before-flush.' . $type; + $writer = new FlatFileWriter(new StaticValueParameterAccessor($file)); + $writer->flush(); + } + + public function types(): \Generator + { + yield ['csv']; + yield ['ods']; + yield ['xlsx']; + } + + /** + * @dataProvider multipleSheetsOptions + */ + public function testWriteMultipleSheets(string $type, callable $options): void + { + $file = self::WRITE_DIR . '/multiple-sheets.' . $type; + self::assertFileDoesNotExist($file); + + $writer = new FlatFileWriter(new StaticValueParameterAccessor($file), $options()); + $writer->setJobExecution(JobExecution::createRoot('123456789', 'export')); + + $writer->initialize(); + $writer->write([ + WriteToSheetItem::array('English', ['John', 'Doe']), + WriteToSheetItem::array('Français', ['Jean', 'Aimar']), + WriteToSheetItem::row('English', Row::fromValues(['Jack', 'Doe'])), + WriteToSheetItem::row('Français', Row::fromValues(['Jacques', 'Ouzi'])), + ]); + $writer->flush(); + + if ($type === 'csv') { + self::assertFileContents($file, << fn() => new CSVOptions(), + 'xlsx' => fn() => new XLSXOptions('English'), + 'ods' => fn() => new ODSOptions('English'), + ]; + foreach ($types as $type => $options) { + yield [$type, $options]; + } + } + + /** + * @dataProvider wrongOptions + */ + public function testWrongOptions(string $type, callable $options): void + { + $this->expectException(\TypeError::class); + + $file = self::WRITE_DIR . '/should-initialize-before-flush.' . $type; + $jobExecution = JobExecution::createRoot('123456789', 'parent'); + $reader = new FlatFileWriter(new StaticValueParameterAccessor($file), $options()); + $reader->setJobExecution($jobExecution); + $reader->initialize(); + } + + public function wrongOptions(): \Generator + { + // with CSV file, CSVOptions is expected + yield [ + 'csv', + fn() => new XLSXOptions(), + ]; + yield [ + 'csv', + fn() => new ODSOptions(), + ]; + + // with ODS file, ODSOptions is expected + yield [ + 'ods', + fn() => new CSVOptions(), + ]; + yield [ + 'ods', + fn() => new XLSXOptions(), + ]; + + // with XLSX file, XLSXOptions is expected + yield [ + 'xlsx', + fn() => new CSVOptions(), + ]; + yield [ + 'xlsx', + fn() => new ODSOptions(), + ]; + } + + private static function assertFileContents(string $filePath, string $inlineData): void + { + $type = \strtolower(\pathinfo($filePath, PATHINFO_EXTENSION)); + $strings = array_merge(...array_map('str_getcsv', explode(PHP_EOL, $inlineData))); + + switch ($type) { + case 'csv': + $fileContents = file_get_contents($filePath); + foreach ($strings as $string) { + self::assertStringContainsString($string, $fileContents); + } + break; + + case 'xlsx': + $pathToSheetFile = $filePath . '#xl/worksheets/sheet1.xml'; + $xmlContents = file_get_contents('zip://' . $pathToSheetFile); + foreach ($strings as $string) { + self::assertStringContainsString("$string", $xmlContents); + } + break; + + case 'ods': + $sheetContent = file_get_contents('zip://' . $filePath . '#content.xml'); + if (!preg_match('#]+>[\s\S]*?<\/table:table>#', $sheetContent, $matches)) { + self::fail('No sheet found in file "' . $filePath . '".'); + } + $sheetXmlAsString = $matches[0]; + foreach ($strings as $string) { + self::assertStringContainsString("$string", $sheetXmlAsString); + } + break; + } + } + + private static function assertSheetContents(string $filePath, string $sheet, string $inlineData): void + { + $type = \strtolower(\pathinfo($filePath, PATHINFO_EXTENSION)); + $strings = array_merge(...array_map('str_getcsv', explode(PHP_EOL, $inlineData))); + + switch ($type) { + case 'csv': + $fileContents = file_get_contents($filePath); + foreach ($strings as $string) { + self::assertStringContainsString($string, $fileContents); + } + break; + + case 'xlsx': + $workbookContent = file_get_contents('zip://' . $filePath . '#xl/workbook.xml'); + if (!preg_match('#$string", $sheetContent); + } + break; + + case 'ods': + $sheetContent = file_get_contents('zip://' . $filePath . '#content.xml'); + $regex = '#[\s\S]*?<\/table:table>#'; + if (!preg_match($regex, $sheetContent, $matches)) { + self::fail('Sheet ' . $sheet . ' was not found in file "' . $filePath . '".'); + } + $sheetXmlAsString = $matches[0]; + foreach ($strings as $string) { + self::assertStringContainsString("$string", $sheetXmlAsString); + } + break; + } + } +} diff --git a/src/batch-openspout/tests/bootstrap.php b/src/batch-openspout/tests/bootstrap.php new file mode 100644 index 00000000..6dc2ab0c --- /dev/null +++ b/src/batch-openspout/tests/bootstrap.php @@ -0,0 +1,22 @@ +remove($artifactDir); +} + +(new Filesystem())->mkdir($artifactDir); + +define('ARTIFACT_DIR', $artifactDir); diff --git a/src/batch/README.md b/src/batch/README.md index bb358a12..1f13ddff 100644 --- a/src/batch/README.md +++ b/src/batch/README.md @@ -39,10 +39,11 @@ Looking for something in particular ? Looking for something more specific ? -- [Read/Write from/to CSV/ODS/XLSX](https://github.com/yokai-php/batch-box-spout) - [Store job executions in relational database](https://github.com/yokai-php/batch-doctrine-dbal) - [Read from Doctrine ORM entities](https://github.com/yokai-php/batch-doctrine-orm) - [Write to Doctrine ORM/ODM... objects](https://github.com/yokai-php/batch-doctrine-persistence) +- [Copy/Move files in a job / Trigger job when file found](https://github.com/yokai-php/batch-league-flysystem) +- [Read/Write from/to CSV/ODS/XLSX](https://github.com/yokai-php/batch-box-spout) - [Trigger async jobs using CLI command](https://github.com/yokai-php/batch-symfony-console): - [Integration with Symfony framework](https://github.com/yokai-php/batch-symfony-framework) - [Trigger async jobs using using queue](https://github.com/yokai-php/batch-symfony-messenger): diff --git a/src/batch/docs/domain/item-job/item-reader.md b/src/batch/docs/domain/item-job/item-reader.md index b0a4210c..fe20c720 100644 --- a/src/batch/docs/domain/item-job/item-reader.md +++ b/src/batch/docs/domain/item-job/item-reader.md @@ -23,7 +23,9 @@ It can be any class implementing [ItemReaderInterface](../../../src/Job/Item/Ite read from an iterable you provide during construction. **Item readers from bridges:** -- [FlatFileReader (`box/spout`)](https://github.com/yokai-php/batch-box-spout/blob/0.x/src/Reader/FlatFileReader.php): +- `DEPRECATED` [FlatFileReader (`box/spout`)](https://github.com/yokai-php/batch-box-spout/blob/0.x/src/Reader/FlatFileReader.php): + read from any CSV/ODS/XLSX file. +- [FlatFileReader (`openspout/openspout`)](https://github.com/yokai-php/batch-openspout/blob/0.x/src/Reader/FlatFileReader.php): read from any CSV/ODS/XLSX file. - [DoctrineDBALQueryOffsetReader (`doctrine/dbal`)](https://github.com/yokai-php/batch-doctrine-dbal/blob/0.x/src/DoctrineDBALQueryOffsetReader.php): read execute an SQL query and iterate over results, using a limit + offset pagination strategy. diff --git a/src/batch/docs/domain/item-job/item-writer.md b/src/batch/docs/domain/item-job/item-writer.md index 61d0dc9c..58ff04ce 100644 --- a/src/batch/docs/domain/item-job/item-writer.md +++ b/src/batch/docs/domain/item-job/item-writer.md @@ -35,7 +35,9 @@ It can be any class implementing [ItemWriterInterface](../../../src/Job/Item/Ite write items by inserting/updating in a table via a Doctrine `Connection`. - [ObjectWriter (`doctrine/persistence`)](https://github.com/yokai-php/batch-doctrine-persistence/blob/0.x/src/ObjectWriter.php): write items to any Doctrine `ObjectManager`. -- [FlatFileWriter (`box/spout`)](https://github.com/yokai-php/batch-box-spout/blob/0.x/src/Writer/FlatFileWriter.php): +- `DEPRECATED` [FlatFileWriter (`box/spout`)](https://github.com/yokai-php/batch-box-spout/blob/0.x/src/Writer/FlatFileWriter.php): + write items to any CSV/ODS/XLSX file. +- [FlatFileWriter (`openspout/openspout`)](https://github.com/yokai-php/batch-openspout/blob/0.x/src/Writer/FlatFileWriter.php): write items to any CSV/ODS/XLSX file. **Item writers for testing purpose:** diff --git a/tests/integration/ImportDevelopersXlsxToORMTest.php b/tests/integration/ImportDevelopersXlsxToORMTest.php index f3879175..4167ef86 100644 --- a/tests/integration/ImportDevelopersXlsxToORMTest.php +++ b/tests/integration/ImportDevelopersXlsxToORMTest.php @@ -12,10 +12,9 @@ use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; -use Yokai\Batch\Bridge\Box\Spout\Reader\FlatFileReader; -use Yokai\Batch\Bridge\Box\Spout\Reader\HeaderStrategy; -use Yokai\Batch\Bridge\Box\Spout\Reader\Options\CSVOptions; use Yokai\Batch\Bridge\Doctrine\Persistence\ObjectWriter; +use Yokai\Batch\Bridge\OpenSpout\Reader\FlatFileReader; +use Yokai\Batch\Bridge\OpenSpout\Reader\HeaderStrategy; use Yokai\Batch\Job\Item\ItemJob; use Yokai\Batch\Job\JobInterface; use Yokai\Batch\Job\JobWithChildJobs; @@ -85,9 +84,8 @@ protected function createJob(JobExecutionStorageInterface $executionStorage): Jo $csvReader = function (string $file): FlatFileReader { return new FlatFileReader( - new StaticValueParameterAccessor($file), - new CSVOptions(), - HeaderStrategy::combine() + filePath: new StaticValueParameterAccessor($file), + headerStrategy: HeaderStrategy::combine(), ); }; diff --git a/tests/integration/Job/SplitDeveloperXlsxJob.php b/tests/integration/Job/SplitDeveloperXlsxJob.php index 58be587a..fd8ee904 100644 --- a/tests/integration/Job/SplitDeveloperXlsxJob.php +++ b/tests/integration/Job/SplitDeveloperXlsxJob.php @@ -4,12 +4,10 @@ namespace Yokai\Batch\Sources\Tests\Integration\Job; -use Box\Spout\Common\Entity\Row; -use Box\Spout\Common\Type; -use Box\Spout\Reader\Common\Creator\ReaderFactory; -use Box\Spout\Reader\SheetInterface; -use Yokai\Batch\Bridge\Box\Spout\Writer\FlatFileWriter; -use Yokai\Batch\Bridge\Box\Spout\Writer\Options\CSVOptions; +use OpenSpout\Common\Entity\Row; +use OpenSpout\Reader\SheetInterface; +use OpenSpout\Reader\XLSX\Reader; +use Yokai\Batch\Bridge\OpenSpout\Writer\FlatFileWriter; use Yokai\Batch\Job\JobInterface; use Yokai\Batch\Job\Parameters\StaticValueParameterAccessor; use Yokai\Batch\JobExecution; @@ -30,7 +28,7 @@ public function execute(JobExecution $jobExecution): void $repositories = []; $developers = []; - $reader = ReaderFactory::createFromType(Type::XLSX); + $reader = new Reader(); $reader->open($this->inputFile); $sheets = iterator_to_array($reader->getSheetIterator(), false); [$badgeSheet, $repositorySheet] = $sheets; @@ -81,7 +79,7 @@ public function execute(JobExecution $jobExecution): void private function writeToCsv(string $filename, array $data, array $headers): void { - $writer = new FlatFileWriter(new StaticValueParameterAccessor($filename), new CSVOptions(), $headers); + $writer = new FlatFileWriter(new StaticValueParameterAccessor($filename), null, $headers); $writer->setJobExecution(JobExecution::createRoot('fake', 'fake')); $writer->initialize(); $writer->write($data); diff --git a/tests/symfony/src/Job/Country/CountryJob.php b/tests/symfony/src/Job/Country/CountryJob.php index d476d116..b3464484 100644 --- a/tests/symfony/src/Job/Country/CountryJob.php +++ b/tests/symfony/src/Job/Country/CountryJob.php @@ -5,8 +5,7 @@ namespace Yokai\Batch\Sources\Tests\Symfony\App\Job\Country; use Symfony\Component\HttpKernel\KernelInterface; -use Yokai\Batch\Bridge\Box\Spout\Writer\FlatFileWriter; -use Yokai\Batch\Bridge\Box\Spout\Writer\Options\CSVOptions; +use Yokai\Batch\Bridge\OpenSpout\Writer\FlatFileWriter; use Yokai\Batch\Bridge\Symfony\Framework\JobWithStaticNameInterface; use Yokai\Batch\Job\AbstractDecoratedJob; use Yokai\Batch\Job\Item\ElementConfiguratorTrait; @@ -79,7 +78,7 @@ public function __construct(JobExecutionStorageInterface $executionStorage, Kern $headers = \array_merge(['iso2'], $fragments); $this->writer = new ChainWriter([ new SummaryWriter(new StaticValueParameterAccessor('countries')), - new FlatFileWriter($writePath('csv'), new CSVOptions(), $headers), + new FlatFileWriter($writePath('csv'), null, $headers), new JsonLinesWriter($writePath('jsonl')), ]); diff --git a/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php b/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php index 60803d10..054a95c5 100644 --- a/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php +++ b/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php @@ -7,9 +7,8 @@ use Closure; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Validator\Validator\ValidatorInterface; -use Yokai\Batch\Bridge\Box\Spout\Reader\FlatFileReader; -use Yokai\Batch\Bridge\Box\Spout\Reader\HeaderStrategy; -use Yokai\Batch\Bridge\Box\Spout\Reader\Options\CSVOptions; +use Yokai\Batch\Bridge\OpenSpout\Reader\FlatFileReader; +use Yokai\Batch\Bridge\OpenSpout\Reader\HeaderStrategy; use Yokai\Batch\Bridge\Doctrine\Persistence\ObjectWriter; use Yokai\Batch\Bridge\Symfony\Framework\JobWithStaticNameInterface; use Yokai\Batch\Bridge\Symfony\Validator\SkipInvalidItemProcessor; @@ -50,7 +49,8 @@ public function __construct( 50, // could be much higher, but set this way for demo purpose new FlatFileReader( new StaticValueParameterAccessor($file), - new CSVOptions(), + null, + null, HeaderStrategy::combine() ), new ChainProcessor([ From f61ea79514a48542465fe377ed9c77a3d00ad555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Mon, 26 Jun 2023 15:13:31 +0200 Subject: [PATCH 2/6] Fix phpstan --- phpstan-baseline.neon | 60 +++++++++++++++++++ .../src/Writer/WriteToSheetItem.php | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 13a73f6b..8812407e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -100,6 +100,66 @@ parameters: count: 1 path: src/batch-doctrine-dbal/src/JobExecutionRowNormalizer.php + - + message: "#^Method Yokai\\\\Batch\\\\Bridge\\\\OpenSpout\\\\Reader\\\\FlatFileReader\\:\\:rows\\(\\) has parameter \\$reader with generic interface OpenSpout\\\\Reader\\\\ReaderInterface but does not specify its types\\: T$#" + count: 1 + path: src/batch-openspout/src/Reader/FlatFileReader.php + + - + message: "#^Parameter \\#1 \\$headers of method Yokai\\\\Batch\\\\Bridge\\\\OpenSpout\\\\Reader\\\\HeaderStrategy\\:\\:setHeaders\\(\\) expects array\\, array\\ given\\.$#" + count: 1 + path: src/batch-openspout/src/Reader/FlatFileReader.php + + - + message: "#^Parameter \\#1 \\$options of class OpenSpout\\\\Reader\\\\CSV\\\\Reader constructor expects OpenSpout\\\\Reader\\\\CSV\\\\Options\\|null, OpenSpout\\\\Reader\\\\CSV\\\\Options\\|OpenSpout\\\\Reader\\\\ODS\\\\Options\\|OpenSpout\\\\Reader\\\\XLSX\\\\Options\\|null given\\.$#" + count: 1 + path: src/batch-openspout/src/Reader/FlatFileReader.php + + - + message: "#^Parameter \\#1 \\$options of class OpenSpout\\\\Reader\\\\ODS\\\\Reader constructor expects OpenSpout\\\\Reader\\\\ODS\\\\Options\\|null, OpenSpout\\\\Reader\\\\CSV\\\\Options\\|OpenSpout\\\\Reader\\\\ODS\\\\Options\\|OpenSpout\\\\Reader\\\\XLSX\\\\Options\\|null given\\.$#" + count: 1 + path: src/batch-openspout/src/Reader/FlatFileReader.php + + - + message: "#^Parameter \\#1 \\$options of class OpenSpout\\\\Reader\\\\XLSX\\\\Reader constructor expects OpenSpout\\\\Reader\\\\XLSX\\\\Options\\|null, OpenSpout\\\\Reader\\\\CSV\\\\Options\\|OpenSpout\\\\Reader\\\\ODS\\\\Options\\|OpenSpout\\\\Reader\\\\XLSX\\\\Options\\|null given\\.$#" + count: 1 + path: src/batch-openspout/src/Reader/FlatFileReader.php + + - + message: "#^Parameter \\#1 \\$row of method Yokai\\\\Batch\\\\Bridge\\\\OpenSpout\\\\Reader\\\\HeaderStrategy\\:\\:getItem\\(\\) expects array\\, array\\ given\\.$#" + count: 1 + path: src/batch-openspout/src/Reader/FlatFileReader.php + + - + message: "#^Method Yokai\\\\Batch\\\\Bridge\\\\OpenSpout\\\\Reader\\\\SheetFilter\\:\\:list\\(\\) has parameter \\$reader with generic interface OpenSpout\\\\Reader\\\\ReaderInterface but does not specify its types\\: T$#" + count: 1 + path: src/batch-openspout/src/Reader/SheetFilter.php + + - + message: "#^Method Yokai\\\\Batch\\\\Bridge\\\\OpenSpout\\\\Reader\\\\SheetFilter\\:\\:list\\(\\) return type with generic interface OpenSpout\\\\Reader\\\\SheetInterface does not specify its types\\: T$#" + count: 1 + path: src/batch-openspout/src/Reader/SheetFilter.php + + - + message: "#^PHPDoc tag @var for variable \\$sheet contains generic interface OpenSpout\\\\Reader\\\\SheetInterface but does not specify its types\\: T$#" + count: 1 + path: src/batch-openspout/src/Reader/SheetFilter.php + + - + message: "#^Parameter \\#1 \\$options of class OpenSpout\\\\Writer\\\\CSV\\\\Writer constructor expects OpenSpout\\\\Writer\\\\CSV\\\\Options\\|null, OpenSpout\\\\Writer\\\\CSV\\\\Options\\|OpenSpout\\\\Writer\\\\ODS\\\\Options\\|OpenSpout\\\\Writer\\\\XLSX\\\\Options\\|null given\\.$#" + count: 1 + path: src/batch-openspout/src/Writer/FlatFileWriter.php + + - + message: "#^Parameter \\#1 \\$options of class OpenSpout\\\\Writer\\\\ODS\\\\Writer constructor expects OpenSpout\\\\Writer\\\\ODS\\\\Options\\|null, OpenSpout\\\\Writer\\\\CSV\\\\Options\\|OpenSpout\\\\Writer\\\\ODS\\\\Options\\|OpenSpout\\\\Writer\\\\XLSX\\\\Options\\|null given\\.$#" + count: 1 + path: src/batch-openspout/src/Writer/FlatFileWriter.php + + - + message: "#^Parameter \\#1 \\$options of class OpenSpout\\\\Writer\\\\XLSX\\\\Writer constructor expects OpenSpout\\\\Writer\\\\XLSX\\\\Options\\|null, OpenSpout\\\\Writer\\\\CSV\\\\Options\\|OpenSpout\\\\Writer\\\\ODS\\\\Options\\|OpenSpout\\\\Writer\\\\XLSX\\\\Options\\|null given\\.$#" + count: 1 + path: src/batch-openspout/src/Writer/FlatFileWriter.php + - message: "#^Cannot access offset 'connection' on mixed\\.$#" count: 1 diff --git a/src/batch-openspout/src/Writer/WriteToSheetItem.php b/src/batch-openspout/src/Writer/WriteToSheetItem.php index 466558b7..a9086dc2 100644 --- a/src/batch-openspout/src/Writer/WriteToSheetItem.php +++ b/src/batch-openspout/src/Writer/WriteToSheetItem.php @@ -22,7 +22,7 @@ private function __construct( /** * Static constructor from array data. * - * @param array $item + * @param array $item */ public static function array(string $sheet, array $item, Style $style = null): self { From 08eec70443e570723f0e7e5e2ea52ea6f65eb19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Mon, 26 Jun 2023 15:13:53 +0200 Subject: [PATCH 3/6] Fix checkstyle --- src/batch-openspout/src/Reader/FlatFileReader.php | 2 +- src/batch-openspout/tests/Reader/FlatFileReaderTest.php | 6 +++--- .../src/Job/StarWars/AbstractImportStartWarsEntityJob.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/batch-openspout/src/Reader/FlatFileReader.php b/src/batch-openspout/src/Reader/FlatFileReader.php index 50853e8b..8eb905db 100644 --- a/src/batch-openspout/src/Reader/FlatFileReader.php +++ b/src/batch-openspout/src/Reader/FlatFileReader.php @@ -53,7 +53,7 @@ public function read(): iterable 'csv' => new CSVReader($this->options), 'xlsx' => new XLSXReader($this->options), 'ods' => new ODSReader($this->options), - default => throw new UnsupportedTypeException('No readers supporting the given type: '.$extension), + default => throw new UnsupportedTypeException('No readers supporting the given type: ' . $extension), }; $reader->open($path); diff --git a/src/batch-openspout/tests/Reader/FlatFileReaderTest.php b/src/batch-openspout/tests/Reader/FlatFileReaderTest.php index 614adf09..8756d933 100644 --- a/src/batch-openspout/tests/Reader/FlatFileReaderTest.php +++ b/src/batch-openspout/tests/Reader/FlatFileReaderTest.php @@ -5,12 +5,12 @@ namespace Yokai\Batch\Tests\Bridge\OpenSpout\Reader; use Generator; -use PHPUnit\Framework\TestCase; -use Yokai\Batch\Bridge\OpenSpout\Reader\FlatFileReader; -use Yokai\Batch\Bridge\OpenSpout\Reader\HeaderStrategy; use OpenSpout\Reader\CSV\Options as CSVOptions; use OpenSpout\Reader\ODS\Options as ODSOptions; use OpenSpout\Reader\XLSX\Options as XLSXOptions; +use PHPUnit\Framework\TestCase; +use Yokai\Batch\Bridge\OpenSpout\Reader\FlatFileReader; +use Yokai\Batch\Bridge\OpenSpout\Reader\HeaderStrategy; use Yokai\Batch\Bridge\OpenSpout\Reader\SheetFilter; use Yokai\Batch\Job\Parameters\StaticValueParameterAccessor; use Yokai\Batch\JobExecution; diff --git a/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php b/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php index 054a95c5..105cb603 100644 --- a/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php +++ b/tests/symfony/src/Job/StarWars/AbstractImportStartWarsEntityJob.php @@ -7,9 +7,9 @@ use Closure; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Validator\Validator\ValidatorInterface; +use Yokai\Batch\Bridge\Doctrine\Persistence\ObjectWriter; use Yokai\Batch\Bridge\OpenSpout\Reader\FlatFileReader; use Yokai\Batch\Bridge\OpenSpout\Reader\HeaderStrategy; -use Yokai\Batch\Bridge\Doctrine\Persistence\ObjectWriter; use Yokai\Batch\Bridge\Symfony\Framework\JobWithStaticNameInterface; use Yokai\Batch\Bridge\Symfony\Validator\SkipInvalidItemProcessor; use Yokai\Batch\Job\AbstractDecoratedJob; From 10759321ea6c297635dcb49a949363b8de18b232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Tue, 27 Jun 2023 09:27:25 +0200 Subject: [PATCH 4/6] Re-add ability to set the sheet name during flat file write --- .../src/Writer/FlatFileWriter.php | 8 ++- .../tests/Reader/FlatFileReaderTest.php | 40 +++-------- .../tests/Writer/FlatFileWriterTest.php | 71 ++++++++----------- 3 files changed, 45 insertions(+), 74 deletions(-) diff --git a/src/batch-openspout/src/Writer/FlatFileWriter.php b/src/batch-openspout/src/Writer/FlatFileWriter.php index 26abb836..1e84f9e1 100644 --- a/src/batch-openspout/src/Writer/FlatFileWriter.php +++ b/src/batch-openspout/src/Writer/FlatFileWriter.php @@ -38,11 +38,11 @@ final class FlatFileWriter implements private ?WriterInterface $writer = null; private bool $headersAdded = false; - private ?string $defaultSheet = null; public function __construct( private JobParameterAccessorInterface $filePath, private CSVOptions|ODSOptions|XLSXOptions|null $options = null, + private string|null $defaultSheet = null, /** * @var list|null */ @@ -69,7 +69,11 @@ public function initialize(): void $this->writer->openToFile($path); if ($this->writer instanceof AbstractWriterMultiSheets) { - $this->defaultSheet = $this->writer->getCurrentSheet()->getName(); + if ($this->defaultSheet !== null) { + $this->writer->getCurrentSheet()->setName($this->defaultSheet); + } else { + $this->defaultSheet = $this->writer->getCurrentSheet()->getName(); + } } } diff --git a/src/batch-openspout/tests/Reader/FlatFileReaderTest.php b/src/batch-openspout/tests/Reader/FlatFileReaderTest.php index 8756d933..32626b4f 100644 --- a/src/batch-openspout/tests/Reader/FlatFileReaderTest.php +++ b/src/batch-openspout/tests/Reader/FlatFileReaderTest.php @@ -22,7 +22,7 @@ class FlatFileReaderTest extends TestCase */ public function testRead( string $file, - ?callable $options, + ?object $options, ?callable $sheetFilter, ?callable $headers, array $expected, @@ -30,7 +30,7 @@ public function testRead( $jobExecution = JobExecution::createRoot('123456789', 'parent'); $reader = new FlatFileReader( new StaticValueParameterAccessor($file), - $options ? $options() : null, + $options, $sheetFilter ? $sheetFilter() : null, $headers ? $headers() : null ); @@ -120,7 +120,7 @@ public function sets(): Generator $options->ENCODING = 'ISO-8859-1'; yield [ __DIR__ . '/fixtures/iso-8859-1.csv', - fn() => $options, + $options, null, null, [ @@ -222,12 +222,12 @@ public function testReadWrongLineSize(): void /** * @dataProvider wrongOptions */ - public function testWrongOptions(string $file, callable $options): void + public function testWrongOptions(string $file, object $options): void { $this->expectException(\TypeError::class); $jobExecution = JobExecution::createRoot('123456789', 'parent'); - $reader = new FlatFileReader(new StaticValueParameterAccessor($file), $options()); + $reader = new FlatFileReader(new StaticValueParameterAccessor($file), $options); $reader->setJobExecution($jobExecution); iterator_to_array($reader->read()); @@ -236,33 +236,15 @@ public function testWrongOptions(string $file, callable $options): void public function wrongOptions(): \Generator { // with CSV file, CSVOptions is expected - yield [ - __DIR__ . '/fixtures/sample.csv', - fn() => new XLSXOptions(), - ]; - yield [ - __DIR__ . '/fixtures/sample.csv', - fn() => new ODSOptions(), - ]; + yield [__DIR__ . '/fixtures/sample.csv', new XLSXOptions()]; + yield [__DIR__ . '/fixtures/sample.csv', new ODSOptions()]; // with ODS file, ODSOptions is expected - yield [ - __DIR__ . '/fixtures/sample.ods', - fn() => new CSVOptions(), - ]; - yield [ - __DIR__ . '/fixtures/sample.ods', - fn() => new XLSXOptions(), - ]; + yield [__DIR__ . '/fixtures/sample.ods', new CSVOptions()]; + yield [__DIR__ . '/fixtures/sample.ods', new XLSXOptions()]; // with XLSX file, XLSXOptions is expected - yield [ - __DIR__ . '/fixtures/sample.xlsx', - fn() => new CSVOptions(), - ]; - yield [ - __DIR__ . '/fixtures/sample.xlsx', - fn() => new ODSOptions(), - ]; + yield [__DIR__ . '/fixtures/sample.xlsx', new CSVOptions()]; + yield [__DIR__ . '/fixtures/sample.xlsx', new ODSOptions()]; } } diff --git a/src/batch-openspout/tests/Writer/FlatFileWriterTest.php b/src/batch-openspout/tests/Writer/FlatFileWriterTest.php index c8e321e0..0ff3aa15 100644 --- a/src/batch-openspout/tests/Writer/FlatFileWriterTest.php +++ b/src/batch-openspout/tests/Writer/FlatFileWriterTest.php @@ -29,7 +29,8 @@ class FlatFileWriterTest extends TestCase */ public function testWrite( string $filename, - ?callable $options, + ?object $options, + ?string $defaultSheet, ?array $headers, iterable $itemsToWrite, string $expectedContent @@ -37,7 +38,7 @@ public function testWrite( $file = self::WRITE_DIR . '/' . $filename; self::assertFileDoesNotExist($file); - $writer = new FlatFileWriter(new StaticValueParameterAccessor($file), $options ? $options() : null, $headers); + $writer = new FlatFileWriter(new StaticValueParameterAccessor($file), $options, $defaultSheet, $headers); $writer->setJobExecution(JobExecution::createRoot('123456789', 'export')); $writer->initialize(); @@ -72,12 +73,14 @@ public function sets(): \Generator "no-header.$type", null, null, + null, $items, $contentWithoutHeader, ]; yield [ "with-header.$type", null, + null, $headers, $items, $contentWithHeader, @@ -94,7 +97,8 @@ public function sets(): \Generator CSV; yield [ "custom.csv", - fn() => $options, + $options, + null, null, $items, $content, @@ -112,7 +116,8 @@ public function sets(): \Generator $options->DEFAULT_ROW_STYLE = $style; yield [ "total-style.xlsx", - fn() => $options, + $options, + 'Sheet1 with styles', null, $items, $contentWithoutHeader, @@ -121,7 +126,8 @@ public function sets(): \Generator $options->DEFAULT_ROW_STYLE = $style; yield [ "total-style.ods", - fn() => $options, + $options, + 'Sheet1 with styles', null, $items, $contentWithoutHeader, @@ -145,6 +151,7 @@ public function sets(): \Generator "partial-style.xlsx", null, null, + null, $styledItems, $contentWithoutHeader, ]; @@ -152,6 +159,7 @@ public function sets(): \Generator "partial-style.ods", null, null, + null, $styledItems, $contentWithoutHeader, ]; @@ -218,14 +226,14 @@ public function types(): \Generator } /** - * @dataProvider multipleSheetsOptions + * @dataProvider multipleSheets */ - public function testWriteMultipleSheets(string $type, callable $options): void + public function testWriteMultipleSheets(string $type, ?string $defaultSheet): void { $file = self::WRITE_DIR . '/multiple-sheets.' . $type; self::assertFileDoesNotExist($file); - $writer = new FlatFileWriter(new StaticValueParameterAccessor($file), $options()); + $writer = new FlatFileWriter(new StaticValueParameterAccessor($file), null, $defaultSheet); $writer->setJobExecution(JobExecution::createRoot('123456789', 'export')); $writer->initialize(); @@ -256,28 +264,23 @@ public function testWriteMultipleSheets(string $type, callable $options): void } } - public function multipleSheetsOptions(): \Generator + public function multipleSheets(): \Generator { - $types = [ - 'csv' => fn() => new CSVOptions(), - 'xlsx' => fn() => new XLSXOptions('English'), - 'ods' => fn() => new ODSOptions('English'), - ]; - foreach ($types as $type => $options) { - yield [$type, $options]; - } + yield ['csv', null]; + yield ['xlsx', 'English']; + yield ['ods', 'English']; } /** * @dataProvider wrongOptions */ - public function testWrongOptions(string $type, callable $options): void + public function testWrongOptions(string $type, object $options): void { $this->expectException(\TypeError::class); $file = self::WRITE_DIR . '/should-initialize-before-flush.' . $type; $jobExecution = JobExecution::createRoot('123456789', 'parent'); - $reader = new FlatFileWriter(new StaticValueParameterAccessor($file), $options()); + $reader = new FlatFileWriter(new StaticValueParameterAccessor($file), $options); $reader->setJobExecution($jobExecution); $reader->initialize(); } @@ -285,34 +288,16 @@ public function testWrongOptions(string $type, callable $options): void public function wrongOptions(): \Generator { // with CSV file, CSVOptions is expected - yield [ - 'csv', - fn() => new XLSXOptions(), - ]; - yield [ - 'csv', - fn() => new ODSOptions(), - ]; + yield ['csv', new XLSXOptions()]; + yield ['csv', new ODSOptions()]; // with ODS file, ODSOptions is expected - yield [ - 'ods', - fn() => new CSVOptions(), - ]; - yield [ - 'ods', - fn() => new XLSXOptions(), - ]; + yield ['ods', new CSVOptions()]; + yield ['ods', new XLSXOptions()]; // with XLSX file, XLSXOptions is expected - yield [ - 'xlsx', - fn() => new CSVOptions(), - ]; - yield [ - 'xlsx', - fn() => new ODSOptions(), - ]; + yield ['xlsx', new CSVOptions()]; + yield ['xlsx', new ODSOptions()]; } private static function assertFileContents(string $filePath, string $inlineData): void From a259ef107de4466ac05fe53c4be1b2c14a22f14b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Tue, 27 Jun 2023 09:28:28 +0200 Subject: [PATCH 5/6] Update docs --- .../docs/flat-file-item-reader.md | 26 +++++++++++++------ .../docs/flat-file-item-writer.md | 19 ++++++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/batch-openspout/docs/flat-file-item-reader.md b/src/batch-openspout/docs/flat-file-item-reader.md index 5af12266..ed16ebe4 100644 --- a/src/batch-openspout/docs/flat-file-item-reader.md +++ b/src/batch-openspout/docs/flat-file-item-reader.md @@ -6,21 +6,30 @@ that will read from CSV/ODS/XLSX file and return each line as an array. ```php FIELD_DELIMITER = ';'; +$options->FIELD_ENCLOSURE = '|'; new FlatFileReader( new StaticValueParameterAccessor('/path/to/file.csv'), - new CSVOptions(';', '|'), - HeaderStrategy::none() + $options, + null, + HeaderStrategy::none(), ); // Read .ods file @@ -28,8 +37,9 @@ new FlatFileReader( // Each item will be an array_combine of first line as key and line as values new FlatFileReader( new StaticValueParameterAccessor('/path/to/file.ods'), - new ODSOptions(SheetFilter::nameIs('Sheet name to read')), - HeaderStrategy::combine() + null, + SheetFilter::nameIs('Sheet name to read'), + HeaderStrategy::combine(), ); ``` diff --git a/src/batch-openspout/docs/flat-file-item-writer.md b/src/batch-openspout/docs/flat-file-item-writer.md index f21512fc..92d057ce 100644 --- a/src/batch-openspout/docs/flat-file-item-writer.md +++ b/src/batch-openspout/docs/flat-file-item-writer.md @@ -6,7 +6,10 @@ written its own line. ```php FIELD_DELIMITER = ';'; +$options->FIELD_ENCLOSURE = '|'; new FlatFileWriter( new StaticValueParameterAccessor('/path/to/file.csv'), - new CSVOptions(';', '|') + $options, ); // Write items to .ods file // That file will contain a header line with : static | header | keys // Change the sheet name data will be written // Change the default style of each cell +$options = new ODSOptions(); +$options->DEFAULT_ROW_STYLE = (new Style())->setFontBold(); new FlatFileWriter( new StaticValueParameterAccessor('/path/to/file.ods'), - new ODSOptions('The sheet name', (new StyleBuilder())->setFontBold()->build()), - ['static', 'header', 'keys'] + $options, + 'The sheet name', + ['static', 'header', 'keys'], ); ``` From a7b79ac70ab47372feec3a1d45144533a2f734dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Tue, 27 Jun 2023 10:01:55 +0200 Subject: [PATCH 6/6] Fixed tests --- tests/integration/Job/SplitDeveloperXlsxJob.php | 2 +- tests/symfony/src/Job/Country/CountryJob.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/Job/SplitDeveloperXlsxJob.php b/tests/integration/Job/SplitDeveloperXlsxJob.php index fd8ee904..4c8845c2 100644 --- a/tests/integration/Job/SplitDeveloperXlsxJob.php +++ b/tests/integration/Job/SplitDeveloperXlsxJob.php @@ -79,7 +79,7 @@ public function execute(JobExecution $jobExecution): void private function writeToCsv(string $filename, array $data, array $headers): void { - $writer = new FlatFileWriter(new StaticValueParameterAccessor($filename), null, $headers); + $writer = new FlatFileWriter(new StaticValueParameterAccessor($filename), null, null, $headers); $writer->setJobExecution(JobExecution::createRoot('fake', 'fake')); $writer->initialize(); $writer->write($data); diff --git a/tests/symfony/src/Job/Country/CountryJob.php b/tests/symfony/src/Job/Country/CountryJob.php index b3464484..71870b65 100644 --- a/tests/symfony/src/Job/Country/CountryJob.php +++ b/tests/symfony/src/Job/Country/CountryJob.php @@ -78,7 +78,7 @@ public function __construct(JobExecutionStorageInterface $executionStorage, Kern $headers = \array_merge(['iso2'], $fragments); $this->writer = new ChainWriter([ new SummaryWriter(new StaticValueParameterAccessor('countries')), - new FlatFileWriter($writePath('csv'), null, $headers), + new FlatFileWriter($writePath('csv'), null, null, $headers), new JsonLinesWriter($writePath('jsonl')), ]);