Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC] Value formatters #649

Draft
wants to merge 1 commit into
base: 3.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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``.
12 changes: 12 additions & 0 deletions docs/reference/symfony.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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 +103,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 <thomas.rabaix@sonata-project.org>
*
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this translatable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I think this should be responsibility of something like a TranslationFormatter. WDYT?

) {
}

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