Skip to content

Commit

Permalink
Merge pull request #53 from teamneusta/feature/add-configuration-for-…
Browse files Browse the repository at this point in the history
…converting-populator

Configuration for (converting) Populators
  • Loading branch information
mike4git authored Sep 13, 2023
2 parents 7c20ea6 + 35873e5 commit 0d82ffd
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 8 deletions.
25 changes: 25 additions & 0 deletions config/packages/neusta_converter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
neusta_converter:
converter:
test.person.converter:
# converter: Neusta\ConverterBundle\Converter\GenericConverter
target_factory: Neusta\ConverterBundle\Tests\Fixtures\Model\PersonFactory
populators:
- Neusta\ConverterBundle\Tests\Fixtures\Populator\PersonNamePopulator
# properties:
# fullName: ~
# age: ageInYears
# context:
# group: ~ # same property name
# locale: language # different property names

test.contactnumber.converter:
target_factory: Neusta\ConverterBundle\Tests\Fixtures\Model\ContactNumberFactory
properties:
phoneNumber: number

populators:
test.person.address.populator:
converter: test.address.converter
property:
address: ~

39 changes: 39 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Neusta\ConverterBundle\DependencyInjection;

use Neusta\ConverterBundle\Converter\GenericConverter;
use Neusta\ConverterBundle\Populator;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand All @@ -17,6 +18,7 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode = $treeBuilder->getRootNode();

$this->addConverterSection($rootNode);
$this->addPopulatorSection($rootNode);

return $treeBuilder;
}
Expand Down Expand Up @@ -68,4 +70,41 @@ private function addConverterSection(ArrayNodeDefinition $rootNode): void
->end()
;
}

private function addPopulatorSection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->children()
->arrayNode('populators')
->info('Populator configuration')
->normalizeKeys(false)
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->scalarNode('class')
->info('class of the "Populator" implementation')
->defaultValue(Populator\ConvertingPopulator::class)
->end()
->scalarNode('converter')
->info('Service id of the internal "Converter"')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('property')
->info('Mapping of source property (value) to target property (key)')
->normalizeKeys(false)
->useAttributeAsKey('target')
->prototype('scalar')
->isRequired()
->end()
->end()
->end()
->validate()
->ifTrue(fn (array $c) => empty($c['converter']) && empty($c['property']))
->thenInvalid('A "converter" and "property" must be defined.')
->end()
->end()
->end()
;
}
}
64 changes: 64 additions & 0 deletions src/DependencyInjection/NeustaConverterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
namespace Neusta\ConverterBundle\DependencyInjection;

use Neusta\ConverterBundle\Converter;
use Neusta\ConverterBundle\Populator\ArrayConvertingPopulator;
use Neusta\ConverterBundle\Populator\ContextMappingPopulator;
use Neusta\ConverterBundle\Populator\ConvertingPopulator;
use Neusta\ConverterBundle\Populator\PropertyMappingPopulator;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;

final class NeustaConverterExtension extends ConfigurableExtension
Expand All @@ -26,6 +29,10 @@ public function loadInternal(array $mergedConfig, ContainerBuilder $container):
foreach ($mergedConfig['converter'] as $converterId => $converter) {
$this->registerConverterConfiguration($converterId, $converter, $container);
}

foreach ($mergedConfig['populators'] as $populatorId => $populator) {
$this->registerPopulatorConfiguration($populatorId, $populator, $container);
}
}

/**
Expand Down Expand Up @@ -67,6 +74,63 @@ private function registerConverterConfiguration(string $id, array $config, Conta
]);
}

/**
* @param array<string, mixed> $config
*/
private function registerPopulatorConfiguration(string $id, array $config, ContainerBuilder $container): void
{
$arguments = [];
if (empty($config['class']) || ConvertingPopulator::class === $config['class']) {
$arguments = $this->buildArgumentsForConvertingPopulator($config);
} elseif (ArrayConvertingPopulator::class === $config['class']) {
$arguments = $this->buildArgumentsForArrayConvertingPopulator($config);
}

$container->register($id, $config['class'])
->setPublic(true)
->setArguments($arguments);
}

/**
* @param array<string, mixed> $config
*
* @return array<string, string>
*/
private function buildArgumentsForConvertingPopulator(array $config): array
{
$targetProperty = array_key_first($config['property']);
$sourceProperty = $config['property'][$targetProperty];
$sourceProperty = $sourceProperty ?? $targetProperty;

return
[
'$converter' => new TypedReference($config['converter'], Converter::class),
'$sourcePropertyName' => $sourceProperty,
'$targetPropertyName' => $targetProperty,
'$accessor' => new Reference('property_accessor'),
];
}

/**
* @param array<string, mixed> $config
*
* @return array<string, string>
*/
private function buildArgumentsForArrayConvertingPopulator(array $config): array
{
$innerPropertyArgument = [];
$innerProperty = $config['property']['itemProperty'];
$innerPropertyArgument['$sourceArrayItemPropertyName'] = $innerProperty;
unset($config['property']['itemProperty']);

$arguments = array_merge(
$innerPropertyArgument,
$this->buildArgumentsForConvertingPopulator($config),
);

return $arguments;
}

private function appendSuffix(string $value, string $suffix): string
{
return str_ends_with($value, $suffix) ? $value : $value . $suffix;
Expand Down
119 changes: 119 additions & 0 deletions tests/DependencyInjection/NeustaConverterExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
use Neusta\ConverterBundle\Converter\GenericConverter;
use Neusta\ConverterBundle\DependencyInjection\NeustaConverterExtension;
use Neusta\ConverterBundle\NeustaConverterBundle;
use Neusta\ConverterBundle\Populator\ArrayConvertingPopulator;
use Neusta\ConverterBundle\Populator\ContextMappingPopulator;
use Neusta\ConverterBundle\Populator\ConvertingPopulator;
use Neusta\ConverterBundle\Populator\PropertyMappingPopulator;
use Neusta\ConverterBundle\Tests\Fixtures\Model\PersonFactory;
use Neusta\ConverterBundle\Tests\Fixtures\Populator\PersonNamePopulator;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;

class NeustaConverterExtensionTest extends TestCase
{
Expand Down Expand Up @@ -123,6 +126,122 @@ public function test_with_mapped_context(): void
self::assertSame('age', $ageInYearsPopulator->getArgument('$contextProperty'));
}

public function test_with_converting_populator(): void
{
$container = $this->buildContainer([
'populators' => [
'foobar' => [
'converter' => GenericConverter::class,
'property' => [
'targetTest' => 'sourceTest',
],
],
],
]);

// converter
$populator = $container->getDefinition('foobar');

self::assertSame(ConvertingPopulator::class, $populator->getClass());
self::assertTrue($populator->isPublic());
self::assertInstanceOf(TypedReference::class, $populator->getArgument('$converter'));
self::assertSame('targetTest', $populator->getArgument('$targetPropertyName'));
self::assertSame('sourceTest', $populator->getArgument('$sourcePropertyName'));
}

public function test_with_array_converting_populator(): void
{
$container = $this->buildContainer([
'populators' => [
'foobar' => [
'converter' => GenericConverter::class,
'property' => [
'targetTest' => 'sourceTest',
],
],
],
]);

// converter
$populator = $container->getDefinition('foobar');

self::assertSame(ConvertingPopulator::class, $populator->getClass());
self::assertTrue($populator->isPublic());
self::assertInstanceOf(TypedReference::class, $populator->getArgument('$converter'));
self::assertSame('targetTest', $populator->getArgument('$targetPropertyName'));
self::assertSame('sourceTest', $populator->getArgument('$sourcePropertyName'));
}

public function test_with_array_converting_populator_with_inner_property(): void
{
$container = $this->buildContainer([
'populators' => [
'foobar' => [
'class' => ArrayConvertingPopulator::class,
'converter' => GenericConverter::class,
'property' => [
'targetTest' => 'sourceTest',
'itemProperty' => 'value',
],
],
],
]);

// converter
$populator = $container->getDefinition('foobar');

self::assertSame(ArrayConvertingPopulator::class, $populator->getClass());
self::assertSame('value', $populator->getArgument('$sourceArrayItemPropertyName'));
}

public function test_with_array_converting_populator_with_inner_property_same_name(): void
{
$container = $this->buildContainer([
'populators' => [
'foobar' => [
'class' => ArrayConvertingPopulator::class,
'converter' => GenericConverter::class,
'property' => [
'test' => null, // in yaml one will write ~
'itemProperty' => 'value',
],
],
],
]);

// converter
$populator = $container->getDefinition('foobar');

self::assertSame(ArrayConvertingPopulator::class, $populator->getClass());
self::assertSame('test', $populator->getArgument('$targetPropertyName'));
self::assertSame('test', $populator->getArgument('$sourcePropertyName'));
self::assertSame('value', $populator->getArgument('$sourceArrayItemPropertyName'));
}

public function test_with_array_converting_populator_with_inner_property_first(): void
{
$container = $this->buildContainer([
'populators' => [
'foobar' => [
'class' => ArrayConvertingPopulator::class,
'converter' => GenericConverter::class,
'property' => [
'itemProperty' => 'value',
'targetTest' => 'sourceTest',
],
],
],
]);

// converter
$populator = $container->getDefinition('foobar');

self::assertSame(ArrayConvertingPopulator::class, $populator->getClass());
self::assertSame('targetTest', $populator->getArgument('$targetPropertyName'));
self::assertSame('sourceTest', $populator->getArgument('$sourcePropertyName'));
self::assertSame('value', $populator->getArgument('$sourceArrayItemPropertyName'));
}

private static function assertIsReference(string $expected, mixed $actual): void
{
self::assertInstanceOf(Reference::class, $actual);
Expand Down
7 changes: 7 additions & 0 deletions tests/app/config/packages/neusta_converter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ neusta_converter:
target_factory: Neusta\ConverterBundle\Tests\Fixtures\Model\ContactNumberFactory
properties:
phoneNumber: number

populators:
test.person.address.populator:
converter: test.address.converter
property:
address: ~

8 changes: 0 additions & 8 deletions tests/app/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@ services:
Neusta\ConverterBundle\Tests\Fixtures\Populator\PersonNamePopulator: ~
Neusta\ConverterBundle\Tests\Fixtures\Populator\AddressPopulator: ~

test.person.address.populator:
parent: 'neusta_converter.converting_populator'
public: true
arguments:
$converter: '@test.address.converter'
$sourcePropertyName: 'address'
$targetPropertyName: 'address'

test.person.wrong.source.type.populator:
parent: 'neusta_converter.converting_populator'
public: true
Expand Down

0 comments on commit 0d82ffd

Please sign in to comment.