Skip to content

Commit

Permalink
Introduce value formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
phansys committed Dec 12, 2023
1 parent 0325ba6 commit 4b54296
Show file tree
Hide file tree
Showing 45 changed files with 1,367 additions and 76 deletions.
31 changes: 31 additions & 0 deletions UPGRADE-3.x.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,33 @@
UPGRADE 3.x
===========

UPGRADE FROM 3.x to 3.x
=======================

## Formatters for writers

- Added `Sonata\Exporter\Formatter\BoolFormatter`, `Sonata\Exporter\Formatter\DateIntervalFormatter`, `Sonata\Exporter\Formatter\DateTimeFormatter`,
`Sonata\Exporter\Formatter\EnumFormatter`, `Sonata\Exporter\Formatter\IterableFormatter`, `Sonata\Exporter\Formatter\StringableFormatter` and
`Sonata\Exporter\Formatter\SymfonyTranslationFormatter`
classes to be used within implementations of `Sonata\Exporter\Formatter\Writer\FormatAwareInterface`.
- Deprecated `Sonata\Exporter\Writer\FormattedBoolWriter`, use `Sonata\Exporter\Formatter\BoolFormatter` instead.
- Deprecated arguments `dateTimeFormat` and `useBackedEnumValue` in `Sonata\Exporter\Source\AbstractPropertySourceIterator::__construct()` and
their children classes. To disable the source formatting you MUST pass `true` in argument `disableSourceFormatters` and use
`Sonata\Exporter\Formatter\Writer\FormatAwareInterface::addFormatter()` in your writers instead.

## Symfony Bridge

- Added `sonata_exporter.writers.{writer}.formatters` configuration in order to determine which formatters will be used by each writer.

```yaml
sonata_exporter:
writers:
csv:
formatters:
- datetime
- enum
# - ...
```

By default, "bool", "dateinterval", "datetime", "enum", "iterable" and "stringable" formatters are configured.
If "symfony/translations-contracts" is installed, "symfony_translator" formatter is also enabled.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"symfony/phpunit-bridge": "^6.2 || ^7.0",
"symfony/property-access": "^5.4 || ^6.2 || ^7.0",
"symfony/routing": "^5.4 || ^6.2 || ^7.0",
"symfony/translation-contracts": "^3.0.2",
"vimeo/psalm": "^5.0"
},
"conflict": {
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
===============
Sonata exporter
Sonata Exporter
===============

Sonata exporter is a library to export data from one source to an output in an efficient way.
Sonata Exporter is a library to export data from one source to an output in an efficient way.
It is highly performance-oriented.

Summary
Expand Down
12 changes: 9 additions & 3 deletions docs/reference/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ Installation

The easiest way to install is to require it with Composer:

.. code-block:: bash
.. code-block:: shell
composer require sonata-project/exporter
For support of the XLSX format, require this package with Composer:
For support of the XLSX format, require this package:

.. code-block:: bash
.. code-block:: shell
composer require phpoffice/phpspreadsheet
If you need the ``SymfonyTranslationFormatter`` formatter, require this package:

.. code-block:: shell
composer require symfony/translation-contracts
4 changes: 2 additions & 2 deletions docs/reference/introduction.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
===============
Sonata exporter
Sonata Exporter
===============

Sonata exporter allows you to convert large amount of data from a source to an output format
Sonata Exporter allows you to convert large amount of data from a source to an output format
(most generally to a file) by streaming it (hence avoiding too much memory consumption).

Usage
Expand Down
39 changes: 29 additions & 10 deletions docs/reference/outputs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,36 @@
Outputs
=======

Several output formatters are supported:
Several output writers are supported:

* CSV
* GSA Feed (Google Search Appliance)
* `CSV`_
* `GSA Feed`_ (Google Search Appliance)
* In Memory (for test purposes mostly)
* JSON
* Sitemap
* XML
* Excel XML
* XLSX (SpreadsheetML format for Microsoft Excel)
* `JSON`_
* `Sitemap`_ (Sitemaps XML)
* `XLS XML`_ (Microsoft Excel 5.0/95 Workbook)
* `XLSX`_ (Excel Workbook)
* `XML`_

You may also create your own. To do so, simply create a class that implements the ``Exporter\Writer\WriterInterface``,
or better, if you know what ``Content-Type`` header should be used along with
your output and what format it produces, ``TypedWriterInterface``.
or better, if you know what ``Content-Type`` header should be used along with your output and what format it produces, ``TypedWriterInterface``.

You can transform the output through the following formatters:

* ``BoolFormatter``: Transforms boolean values to the configured strings (defaults to ``true`` => "yes", ``false`` => "no")
* ``DateIntervalFormatter``: Transforms ``\DateInterval`` objects to their ISO-8601 duration representation
* ``DateTimeFormatter``: Transforms ``\DateTimeInterface`` objects to the configured date representation (defaults to ``\DateTimeInterface::RFC2822``)
* ``EnumFormatter``: Transforms enumeration cases to a string representation (from the enum cases or values) depending on the enumeration type
(``\UnitEnum`` or ``\BackedEnum``) and the ``$useBackedEnumValue`` parameter (defaults to ``true``)
* ``IterableFormatter``: Transforms an iterable value to their string representation
* ``StringableFormatter``: Transforms stringable objects to their string representation (the one configured in the ``__toString()`` method)
* ``SymfonyTranslationFormatter``: Transforms messages (strings or objects implementing ``TranslatableInterface``) into their translation based
on the given configuration (parameters, domain, locale). It requires the "symfony/translation-contracts" package.

.. _`CSV`: https://datatracker.ietf.org/doc/html/rfc4180
.. _`GSA Feed`: https://developers.google.com/search-appliance
.. _`JSON`: https://www.json.org/json-en.html
.. _`Sitemap`: https://www.sitemaps.org/protocol.html
.. _`XLS XML`: https://support.microsoft.com/en-us/office/file-formats-that-are-supported-in-excel-0943ff2c-6014-4e8d-aaea-b83d51d46247#ID0EDT
.. _`XLSX`: https://support.microsoft.com/en-us/office/file-formats-that-are-supported-in-excel-0943ff2c-6014-4e8d-aaea-b83d51d46247#ID0EDT
.. _`XML`: https://www.w3.org/TR/xml/
8 changes: 4 additions & 4 deletions docs/reference/sources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ Sources

You may export data from various sources:

* PHP Array
* Chain (can aggregate data from several different iterators)
* CSV
* Doctrine Query (ORM & ODM supported)
* PDO Statement
* PHP Array
* PHP Iterator instance
* PHP Iterator with a callback on current
* Sitemap (Takes another iterator)
* XML
* Excel XML
* XLS XML
* XLSX (SpreadsheetML format for Microsoft Excel)
* Sitemap (Takes another iterator)
* Chain (can aggregate data from several different iterators)

You may also create your own. To do so, create a class that implements ``\Iterator``.
15 changes: 15 additions & 0 deletions docs/reference/symfony.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ Enable the Bundle

Now, enable the bundle in ``bundles.php`` file::

.. code-block:: php
// config/bundles.php
return [
// ...
Sonata\Exporter\Bridge\Symfony\SonataExporterBundle::class => ['all' => true],
];
The default writers
-------------------

Expand All @@ -38,6 +41,7 @@ Each service parameter has a configuration counterpart:
The CSV writer service
~~~~~~~~~~~~~~~~~~~~~~

This service can be configured through the following parameters:

* ``sonata.exporter.writer.csv.filename``: defaults to ``php://output``
Expand Down Expand Up @@ -102,3 +106,14 @@ The default writers list can be altered through configuration:
default_writers:
- csv
- json
The default formatters
----------------------

* ``sonata.exporter.formatter.bool``
* ``sonata.exporter.formatter.dateinterval``
* ``sonata.exporter.formatter.datetime``
* ``sonata.exporter.formatter.enum``
* ``sonata.exporter.formatter.iterable``
* ``sonata.exporter.formatter.stringable``
* ``sonata.exporter.formatter.symfony_translator``
35 changes: 35 additions & 0 deletions src/Bridge/Symfony/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
* This is the class that validates and merges configuration from your app/config files.
Expand Down Expand Up @@ -75,6 +76,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue(false)
->info('include the byte order mark')
->end()
->arrayNode('formatters')
->defaultValue($this->getDefaultFormatters())
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('json')
Expand All @@ -84,6 +89,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue('php://output')
->info('path to the output file')
->end()
->arrayNode('formatters')
->defaultValue($this->getDefaultFormatters())
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('xls')
Expand All @@ -97,6 +106,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue(true)
->info('add column names as the first line')
->end()
->arrayNode('formatters')
->defaultValue($this->getDefaultFormatters())
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('xlsx')
Expand All @@ -114,6 +127,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue(true)
->info('add filters in the first line')
->end()
->arrayNode('formatters')
->defaultValue($this->getDefaultFormatters())
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('xml')
Expand All @@ -135,6 +152,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue('data')
->info('name of elements corresponding to rows')
->end()
->arrayNode('formatters')
->defaultValue($this->getDefaultFormatters())
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
Expand All @@ -157,4 +178,18 @@ private function getDefaultWriters(): array

return $fields;
}

/**
* @return string[]
*/
private function getDefaultFormatters(): array
{
$formatters = ['bool', 'dateinterval', 'datetime', 'enum', 'iterable', 'stringable'];

if (interface_exists(TranslatorInterface::class)) {
$formatters[] = 'symfony_translator';
}

return $formatters;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

/**
Expand Down Expand Up @@ -60,6 +61,14 @@ private function configureExporter(ContainerBuilder $container, array $config):
private function configureWriters(ContainerBuilder $container, array $config): void
{
foreach ($config as $format => $settings) {
if ($container->hasDefinition('sonata.exporter.writer.'.$format)) {
$writer = $container->getDefinition('sonata.exporter.writer.'.$format);

foreach ($config[$format]['formatters'] as $formatter) {
$writer->addMethodCall('addFormatter', [new Reference('sonata.exporter.formatter.'.$formatter)]);
}
}

foreach ($settings as $key => $value) {
$container->setParameter(sprintf(
'sonata.exporter.writer.%s.%s',
Expand Down
26 changes: 26 additions & 0 deletions src/Bridge/Symfony/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use Sonata\Exporter\Exporter;
use Sonata\Exporter\ExporterInterface;
use Sonata\Exporter\Formatter\BoolFormatter;
use Sonata\Exporter\Formatter\DateIntervalFormatter;
use Sonata\Exporter\Formatter\DateTimeFormatter;
use Sonata\Exporter\Formatter\EnumFormatter;
use Sonata\Exporter\Formatter\IterableFormatter;
use Sonata\Exporter\Formatter\StringableFormatter;
use Sonata\Exporter\Formatter\SymfonyTranslationFormatter;
use Sonata\Exporter\Writer\CsvWriter;
use Sonata\Exporter\Writer\JsonWriter;
use Sonata\Exporter\Writer\XlsWriter;
use Sonata\Exporter\Writer\XlsxWriter;
use Sonata\Exporter\Writer\XmlWriter;
use Symfony\Contracts\Translation\TranslatorInterface;

return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
Expand Down Expand Up @@ -67,4 +75,22 @@

$services->alias(Exporter::class, 'sonata.exporter.exporter');
$services->alias(ExporterInterface::class, 'sonata.exporter.exporter');

$services->set('sonata.exporter.formatter.bool', BoolFormatter::class)
->tag('sonata.exporter.formatter');
$services->set('sonata.exporter.formatter.dateinterval', DateIntervalFormatter::class)
->tag('sonata.exporter.formatter');
$services->set('sonata.exporter.formatter.datetime', DateTimeFormatter::class)
->tag('sonata.exporter.formatter');
$services->set('sonata.exporter.formatter.enum', EnumFormatter::class)
->tag('sonata.exporter.formatter');
$services->set('sonata.exporter.formatter.iterable', IterableFormatter::class)
->tag('sonata.exporter.formatter');
$services->set('sonata.exporter.formatter.stringable', StringableFormatter::class)
->tag('sonata.exporter.formatter');

if (interface_exists(TranslatorInterface::class)) {
$services->set('sonata.exporter.formatter.symfony_translator', SymfonyTranslationFormatter::class)
->tag('sonata.exporter.formatter');
}
};
39 changes: 39 additions & 0 deletions src/Formatter/BoolFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\Exporter\Formatter;

final class BoolFormatter implements FormatterInterface
{
private const LABEL_TRUE = 'yes';
private const LABEL_FALSE = 'no';

public function __construct(
private string $trueLabel = self::LABEL_TRUE,
private string $falseLabel = self::LABEL_FALSE
) {
}

public function format(array $data): array
{
foreach ($data as $key => $value) {
if (!\is_bool($value)) {
continue;
}

$data[$key] = $value ? $this->trueLabel : $this->falseLabel;
}

return $data;
}
}
Loading

0 comments on commit 4b54296

Please sign in to comment.