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 11, 2023
1 parent 0325ba6 commit 6decc95
Show file tree
Hide file tree
Showing 39 changed files with 1,294 additions and 55 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.4",
"vimeo/psalm": "^5.0"
},
"conflict": {
Expand Down
28 changes: 28 additions & 0 deletions src/Bridge/Symfony/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,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 +88,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 +105,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 +126,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 +151,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 +177,12 @@ private function getDefaultWriters(): array

return $fields;
}

/**
* @return string[]
*/
private function getDefaultFormatters(): array
{
return ['bool', 'dateinterval', 'datetime', 'enum', 'iterable', 'stringable'];
}
}
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;
}
}
69 changes: 69 additions & 0 deletions src/Formatter/DateIntervalFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?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 DateIntervalFormatter implements FormatterInterface
{
private const DATE_PARTS = [
'y' => 'Y',
'm' => 'M',
'd' => 'D',
];
private const TIME_PARTS = [
'h' => 'H',
'i' => 'M',
's' => 'S',
];

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

$data[$key] = self::getDuration($value);
}

return $data;
}

/**
* @return string An ISO8601 duration
*/
private static function getDuration(\DateInterval $interval): string
{
$datePart = '';

foreach (self::DATE_PARTS as $datePartAttribute => $datePartAttributeString) {
if ($interval->$datePartAttribute !== 0) {
$datePart .= $interval->$datePartAttribute.$datePartAttributeString;
}
}

$timePart = '';

foreach (self::TIME_PARTS as $timePartAttribute => $timePartAttributeString) {
if ($interval->$timePartAttribute !== 0) {
$timePart .= $interval->$timePartAttribute.$timePartAttributeString;
}
}

if ('' === $datePart && '' === $timePart) {
return 'P0Y';
}

return 'P'.$datePart.('' !== $timePart ? 'T'.$timePart : '');
}
}
35 changes: 35 additions & 0 deletions src/Formatter/DateTimeFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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 DateTimeFormatter implements FormatterInterface
{
public function __construct(
private string $dateTimeFormat = \DateTimeInterface::RFC2822
) {
}

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

$data[$key] = $value->format($this->dateTimeFormat);
}

return $data;
}
}
41 changes: 41 additions & 0 deletions src/Formatter/EnumFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?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 EnumFormatter implements FormatterInterface
{
public function __construct(
private bool $useBackedEnumValue = true
) {
}

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

if ($this->useBackedEnumValue && $value instanceof \BackedEnum) {
$data[$key] = $value->value;

continue;
}

$data[$key] = $value->name;
}

return $data;
}
}
24 changes: 24 additions & 0 deletions src/Formatter/FormatterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?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;

interface FormatterInterface
{
/**
* @param array<int|string, mixed> $data
*
* @return array<int|string, mixed>
*/
public function format(array $data): array;
}
Loading

0 comments on commit 6decc95

Please sign in to comment.