Skip to content
This repository has been archived by the owner on May 16, 2024. It is now read-only.

Commit

Permalink
feat: better private properties handling
Browse files Browse the repository at this point in the history
  • Loading branch information
nikophil committed Sep 21, 2023
1 parent a1f1d81 commit f604af6
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 8 deletions.
2 changes: 2 additions & 0 deletions src/AutoMapperBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use AutoMapper\Bundle\DependencyInjection\Compiler\MapperConfigurationPass;
use AutoMapper\Bundle\DependencyInjection\Compiler\TransformerFactoryPass;
use Jane\Bundle\AutoMapperBundle\DependencyInjection\Compiler\PropertyInfoPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -14,6 +15,7 @@ public function build(ContainerBuilder $container)
parent::build($container);

$container->addCompilerPass(new MapperConfigurationPass());
$container->addCompilerPass(new PropertyInfoPass());
$container->addCompilerPass(new TransformerFactoryPass());
}
}
81 changes: 81 additions & 0 deletions src/DependencyInjection/Compiler/PropertyInfoPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Jane\Bundle\AutoMapperBundle\DependencyInjection\Compiler;

use Jane\Component\AutoMapper\Extractor\MapToContextPropertyInfoExtractorDecorator;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;

class PropertyInfoPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has('property_info')) {
return;
}

$propertyInfoDefinition = $container->findDefinition('property_info');

$container->setDefinition(
'automapper.property_info.reflection_extractor.inner',
new Definition(
ReflectionExtractor::class,
[
'$accessFlags' => ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE,
]
)
);

$container->setDefinition(
'automapper.property_info.reflection_extractor',
new Definition(
MapToContextPropertyInfoExtractorDecorator::class,
[
new Reference('automapper.property_info.reflection_extractor.inner'),
]
)
);

$container->setDefinition(
'automapper.property_info',
new Definition(
PropertyInfoExtractor::class,
[
$this->replaceReflectionExtractor($propertyInfoDefinition->getArgument(0)),
$this->replaceReflectionExtractor($propertyInfoDefinition->getArgument(1)),
$this->replaceReflectionExtractor($propertyInfoDefinition->getArgument(2)),
$this->replaceReflectionExtractor($propertyInfoDefinition->getArgument(3)),
$this->replaceReflectionExtractor($propertyInfoDefinition->getArgument(4)),
]
)
);

$container->setDefinition(
'automapper.property_info.cache',
new Definition(PropertyInfoCacheExtractor::class, [
new Reference('.inner'),
new Reference('cache.property_info'),
])
)->setDecoratedService('automapper.property_info');
}

private function replaceReflectionExtractor(IteratorArgument $extractors): IteratorArgument
{
$newExtractors = [];

/** @var Reference $extractor */
foreach ($extractors->getValues() as $extractor) {
$newExtractors[] = (string) $extractor === 'property_info.reflection_extractor'
? new Reference('automapper.property_info.reflection_extractor')
: $extractor;
}

return new IteratorArgument($newExtractors);
}
}
1 change: 1 addition & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%/automapper')->end()
->scalarNode('date_time_format')->defaultValue(\DateTimeInterface::RFC3339)->end()
->booleanNode('hot_reload')->defaultValue($this->debug)->end()
->booleanNode('map_private_properties')->defaultTrue()->end()
->booleanNode('allow_readonly_target_to_populate')->defaultFalse()->end()
->arrayNode('warmup')
->arrayPrototype()
Expand Down
5 changes: 5 additions & 0 deletions src/DependencyInjection/JaneAutoMapperExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public function load(array $configs, ContainerBuilder $container)
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.xml');

$container->getDefinition(MapperGeneratorMetadataFactory::class)
->replaceArgument(5, $config['date_time_format'])
->replaceArgument(6, $config['map_private_properties'])
;

$container->getDefinition(MapperGeneratorMetadataFactory::class)->replaceArgument(5, $config['date_time_format']);
$container->getDefinition(FileLoader::class)->replaceArgument(2, $config['hot_reload']);
$container->registerForAutoconfiguration(TransformerFactoryInterface::class)->addTag('jane_auto_mapper.transformer_factory');
Expand Down
5 changes: 0 additions & 5 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,5 @@
<argument/> <!-- mappers list from config -->
<tag name="jane_auto_mapper.cache_warmer_loader" />
</service>

<service id="AutoMapper\Extractor\MapToContextPropertyInfoExtractorDecorator">
<argument type="service" id="property_info.reflection_extractor" />
<tag name="property_info.access_extractor" priority="-100" />
</service>
</services>
</container>
33 changes: 33 additions & 0 deletions tests/Fixtures/ClassWithMapToContextAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace AutoMapper\Bundle\tests\Fixtures;

use Jane\Component\AutoMapper\Attribute\MapToContext;

class ClassWithMapToContextAttribute
{
public function __construct(
private string $value,
) {
}

public function getValue(
#[MapToContext('prefix')] string $prefix,
#[MapToContext('suffix')] string $suffix,
): string {
return "{$prefix}_{$this->value}_{$suffix}";
}

public function getVirtualProperty(
#[MapToContext('prefix')] string $prefix,
#[MapToContext('suffix')] string $suffix,
): string {
return "{$prefix}_{$this->value}_{$suffix}";
}

public function getPropertyWithDefaultValue(
string $someVar = 'foo',
): string {
return $someVar;
}
}
15 changes: 15 additions & 0 deletions tests/Fixtures/ClassWithPrivateProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace AutoMapper\Bundle\tests\Fixtures;

class ClassWithPrivateProperty
{
public function __construct(private string $foo)
{
}

private function getBar(): string
{
return 'bar';
}
}
1 change: 1 addition & 0 deletions tests/Resources/app/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ framework:
jane_auto_mapper:
normalizer: true
name_converter: DummyApp\IdNameConverter
map_private_properties: true
warmup:
- {source: 'AutoMapper\Bundle\Tests\Fixtures\NestedObject', target: 'array'}

Expand Down
45 changes: 42 additions & 3 deletions tests/ServiceInstantiationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
use AutoMapper\Bundle\tests\Fixtures\SomeEnum;
use AutoMapper\Bundle\tests\Fixtures\User;
use AutoMapper\Bundle\tests\Fixtures\UserDTO;
use AutoMapper\Bundle\tests\Fixtures\ClassWithPrivateProperty;
use Jane\Component\AutoMapper\AutoMapperInterface;
use Jane\Component\AutoMapper\MapperContext;
use AutoMapper\Bundle\tests\Fixtures\ClassWithMapToContextAttribute;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Filesystem\Filesystem;

Expand Down Expand Up @@ -106,9 +109,6 @@ public function testDiscriminator(): void
self::assertInstanceOf(Fixtures\Cat::class, $pet);
}

/**
* @requires PHP 8.1
*/
public function testItCanMapEnums(): void
{
static::bootKernel();
Expand All @@ -119,4 +119,43 @@ public function testItCanMapEnums(): void
$dto->enum = SomeEnum::FOO;
self::assertSame(['enum' => 'foo'], $autoMapper->map($dto, 'array'));
}

/**
* This test validates that PropertyInfoPass is correctly applied.
*/
public function testMapClassWithPrivateProperty(): void
{
static::bootKernel();
$container = static::$kernel->getContainer();
$autoMapper = $container->get(AutoMapperInterface::class);

self::assertEquals(
new ClassWithPrivateProperty('bar'),
$autoMapper->map(['foo' => 'bar'], ClassWithPrivateProperty::class)
);
}

/**
* We need to test that the mapToContext attribute is correctly used,
* because this behavior is dependent of the dependency injection.
*/
public function testMapToContextAttribute(): void
{
static::bootKernel();
$container = static::$kernel->getContainer();
$autoMapper = $container->get(AutoMapperInterface::class);

self::assertSame(
[
'value' => 'foo_bar_baz',
'virtualProperty' => 'foo_bar_baz',
'propertyWithDefaultValue' => 'foo',
],
$autoMapper->map(
new ClassWithMapToContextAttribute('bar'),
'array',
[MapperContext::MAP_TO_ACCESSOR_PARAMETER => ['suffix' => 'baz', 'prefix' => 'foo']]
)
);
}
}

0 comments on commit f604af6

Please sign in to comment.